tty 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/lib/tty/shell.rb ADDED
@@ -0,0 +1,159 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module TTY
4
+
5
+ # A class responsible for shell prompt interactions.
6
+ class Shell
7
+
8
+ # @api private
9
+ attr_reader :input
10
+
11
+ # @api private
12
+ attr_reader :output
13
+
14
+ # Initialize a Shell
15
+ #
16
+ # @api public
17
+ def initialize(input=stdin, output=stdout)
18
+ @input = input
19
+ @output = output
20
+ end
21
+
22
+ # Ask a question.
23
+ #
24
+ # @example
25
+ # shell = TTY::Shell.new
26
+ # shell.ask("What is your name?")
27
+ #
28
+ # @param [String] statement
29
+ # string question to be asked
30
+ #
31
+ # @yieldparam [TTY::Question] question
32
+ # further configure the question
33
+ #
34
+ # @yield [question]
35
+ #
36
+ # @return [TTY::Question]
37
+ #
38
+ # @api public
39
+ def ask(statement, *args, &block)
40
+ options = Utils.extract_options!(args)
41
+
42
+ question = Question.new self, statement, options
43
+ question.instance_eval(&block) if block_given?
44
+ question.prompt(statement)
45
+ end
46
+
47
+ # A shortcut method to ask the user positive question and return
48
+ # true for 'yes' reply.
49
+ #
50
+ # @return [Boolean]
51
+ #
52
+ # @api public
53
+ def yes?(statement, *args, &block)
54
+ ask(statement, *args, &block).read_bool
55
+ end
56
+
57
+ # A shortcut method to ask the user negative question and return
58
+ # true for 'no' reply.
59
+ #
60
+ # @return [Boolean]
61
+ #
62
+ # @api public
63
+ def no?(statement, *args, &block)
64
+ !yes?(statement, *args, &block)
65
+ end
66
+
67
+ # Print statement out. If the supplied message ends with a space or
68
+ # tab character, a new line will not be appended.
69
+ #
70
+ # @example
71
+ # say("Simple things.")
72
+ #
73
+ # @param [String] message
74
+ #
75
+ # @return [String]
76
+ #
77
+ # @api public
78
+ def say(message="", options={})
79
+ message = message.to_str
80
+ return unless message.length > 0
81
+
82
+ statement = Statement.new(self, options)
83
+ statement.declare message
84
+ end
85
+
86
+ # Print statement(s) out in red green.
87
+ #
88
+ # @example
89
+ # shell.confirm "Are you sure?"
90
+ # shell.confirm "All is fine!", "This is fine too."
91
+ #
92
+ # @param [Array] messages
93
+ #
94
+ # @return [Array] messages
95
+ #
96
+ # @api public
97
+ def confirm(*args)
98
+ options = Utils.extract_options!(args)
99
+ args.each { |message| say message, options.merge(:color => :green) }
100
+ end
101
+
102
+ # Print statement(s) out in yellow color.
103
+ #
104
+ # @example
105
+ # shell.warn "This action can have dire consequences"
106
+ # shell.warn "Carefull young apprentice", "This is potentially dangerous."
107
+ #
108
+ # @param [Array] messages
109
+ #
110
+ # @return [Array] messages
111
+ #
112
+ # @api public
113
+ def warn(*args)
114
+ options = Utils.extract_options!(args)
115
+ args.each { |message| say message, options.merge(:color => :yellow) }
116
+ end
117
+
118
+ # Print statement(s) out in red color.
119
+ #
120
+ # @example
121
+ # shell.error "Shutting down all systems!"
122
+ # shell.error "Nothing is fine!", "All is broken!"
123
+ #
124
+ # @param [Array] messages
125
+ #
126
+ # @return [Array] messages
127
+ #
128
+ # @api public
129
+ def error(*args)
130
+ options = Utils.extract_options!(args)
131
+ args.each { |message| say message, options.merge(:color => :red) }
132
+ end
133
+
134
+ # Print a table to shell.
135
+ #
136
+ # @return [undefined]
137
+ #
138
+ # @api public
139
+ def print_table(*args, &block)
140
+ table = TTY::Table.new *args, &block
141
+ say table.to_s
142
+ end
143
+
144
+ protected
145
+
146
+ def stdin
147
+ $stdin
148
+ end
149
+
150
+ def stdout
151
+ $stdout
152
+ end
153
+
154
+ def stderr
155
+ $stderr
156
+ end
157
+
158
+ end # Shell
159
+ end # TTY
@@ -27,6 +27,12 @@ module TTY
27
27
  end
28
28
  end
29
29
 
30
+ private
31
+
32
+ def actual_length(string)
33
+ string.to_s.gsub(/\e\[\d{1,2}m/, '').length
34
+ end
35
+
30
36
  end # Wrapped
31
37
  end # Operation
32
38
  end # Table
@@ -0,0 +1,143 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module TTY
4
+ class Terminal
5
+
6
+ # A class responsible for coloring strings.
7
+ class Color
8
+
9
+ # Embed in a String to clear all previous ANSI sequences.
10
+ CLEAR = "\e[0m"
11
+ # The start of an ANSI bold sequence.
12
+ BOLD = "\e[1m"
13
+ # The start of an ANSI underlined sequence.
14
+ UNDERLINE = "\e[4m"
15
+
16
+ STYLES = %w[ BOLD CLEAR UNDERLINE ].freeze
17
+
18
+ # Escape codes for text color.
19
+ BLACK = "\e[30m"
20
+ RED = "\e[31m"
21
+ GREEN = "\e[32m"
22
+ YELLOW = "\e[33m"
23
+ BLUE = "\e[34m"
24
+ MAGENTA = "\e[35m"
25
+ CYAN = "\e[36m"
26
+ WHITE = "\e[37m"
27
+
28
+ TEXT_COLORS = %w[ BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE ].freeze
29
+
30
+ # Escape codes for background color.
31
+ ON_BLACK = "\e[40m"
32
+ ON_RED = "\e[41m"
33
+ ON_GREEN = "\e[42m"
34
+ ON_YELLOW = "\e[43m"
35
+ ON_BLUE = "\e[44m"
36
+ ON_MAGENTA = "\e[45m"
37
+ ON_CYAN = "\e[46m"
38
+ ON_WHITE = "\e[47m"
39
+
40
+ BACKGROUND_COLORS = %w[ ON_BLACK ON_RED ON_GREEN ON_YELLOW ON_BLUE ON_MAGENTA ON_CYAN ON_WHITE ].freeze
41
+
42
+ attr_reader :enabled
43
+
44
+ # Initialize a Terminal Color
45
+ #
46
+ # @api public
47
+ def initialize(enabled=false)
48
+ @enabled = enabled
49
+ end
50
+
51
+ # Disable coloring of this terminal session
52
+ #
53
+ # @api public
54
+ def disable!
55
+ @enabled = false
56
+ end
57
+
58
+ # Check if coloring is on
59
+ #
60
+ # @api public
61
+ def enabled?
62
+ @enabled
63
+ end
64
+
65
+ # Apply ANSI color to the given string.
66
+ #
67
+ # @param [String] string
68
+ # text to add ANSI strings
69
+ #
70
+ # @param [Array[Symbol]] colors
71
+ #
72
+ # @example
73
+ # apply "text", :yellow, :on_green, :underline
74
+ #
75
+ # @return [String]
76
+ #
77
+ # @api public
78
+ def set(string, *colors)
79
+ validate *colors
80
+ ansi_colors = colors.map { |color| lookup(color) }
81
+ "#{ansi_colors.join}#{string}#{CLEAR}"
82
+ end
83
+
84
+ # Same as instance method.
85
+ #
86
+ # @return [undefined]
87
+ #
88
+ # @api public
89
+ def self.set(string, *colors)
90
+ new.set(string, *colors)
91
+ end
92
+
93
+ # Remove color codes from a string.
94
+ #
95
+ # @param [String] string
96
+ #
97
+ # @return [String]
98
+ #
99
+ # @api public
100
+ def remove(string)
101
+ string.gsub(/\e\[\d+(;\d+)*m/, '')
102
+ end
103
+
104
+ # Return raw color code without embeding it into a string.
105
+ #
106
+ # @return [Array[String]]
107
+ # ANSI escape codes
108
+ #
109
+ # @api public
110
+ def code(*colors)
111
+ validate *colors
112
+ colors.map { |color| lookup(color) }
113
+ end
114
+
115
+ # All ANSI color names as strings.
116
+ #
117
+ # @return [Array[String]]
118
+ #
119
+ # @api public
120
+ def names
121
+ (STYLES + BACKGROUND_COLORS + TEXT_COLORS).map { |color| color.to_s.downcase }
122
+ end
123
+
124
+ protected
125
+
126
+ # Find color representation.
127
+ #
128
+ # @api private
129
+ def lookup(color)
130
+ self.class.const_get(color.to_s.upcase)
131
+ end
132
+
133
+ # @api private
134
+ def validate(*colors)
135
+ unless colors.all? { |color| names.include?(color.to_s) }
136
+ raise ArgumentError, "Bad color or unintialized constant, valid colors are: #{names.join(', ')}."
137
+ end
138
+ end
139
+
140
+ end # Color
141
+
142
+ end
143
+ end # TTY
data/lib/tty/terminal.rb CHANGED
@@ -3,10 +3,6 @@
3
3
  module TTY
4
4
  class Terminal
5
5
 
6
- @@default_width = 80
7
-
8
- @@default_height = 24
9
-
10
6
  # Return default width of terminal
11
7
  #
12
8
  # @example
@@ -15,31 +11,36 @@ module TTY
15
11
  # @return [Integer]
16
12
  #
17
13
  # @api public
18
- def default_width
19
- @@default_width
20
- end
14
+ attr_reader :default_width
21
15
 
22
- # Set default width of terminal
16
+ # Return default height of terminal
23
17
  #
24
- # @param [Integer] width
18
+ # @example
19
+ # default_height = TTY::Terminal.default_height
25
20
  #
26
21
  # @return [Integer]
27
22
  #
28
23
  # @api public
29
- def default_width=(width)
30
- @@default_width = width
24
+ attr_reader :default_height
25
+
26
+ # @api public
27
+ attr_reader :color
28
+
29
+ def initialize
30
+ @color = TTY::Terminal::Color.new(self.color?)
31
+ @default_width = 80
32
+ @default_height = 24
31
33
  end
32
34
 
33
- # Return default height of terminal
35
+ # Set default width of terminal
34
36
  #
35
- # @example
36
- # default_height = TTY::Terminal.default_height
37
+ # @param [Integer] width
37
38
  #
38
39
  # @return [Integer]
39
40
  #
40
41
  # @api public
41
- def default_height
42
- @@default_height
42
+ def default_width=(width)
43
+ @default_width = width
43
44
  end
44
45
 
45
46
  # Set default height of terminal
@@ -51,7 +52,7 @@ module TTY
51
52
  #
52
53
  # @api public
53
54
  def default_height=(height)
54
- @@default_height = height
55
+ @default_height = height
55
56
  end
56
57
 
57
58
  # Determine current width
@@ -60,8 +61,9 @@ module TTY
60
61
  #
61
62
  # @api width
62
63
  def width
63
- if ENV['TTY_COLUMNS'] =~ /^\d+$/
64
- result = ENV['TTY_COLUMNS'].to_i
64
+ env_tty_columns = ENV['TTY_COLUMNS']
65
+ if env_tty_columns =~ /^\d+$/
66
+ result = env_tty_columns.to_i
65
67
  else
66
68
  result = TTY::System.unix? ? dynamic_width : default_width
67
69
  end
@@ -73,8 +75,9 @@ module TTY
73
75
  #
74
76
  # @api public
75
77
  def height
76
- if ENV['TTY_LINES'] =~ /^\d+$/
77
- result = ENV['TTY_LINES'].to_i
78
+ env_tty_lines = ENV['TTY_LINES']
79
+ if env_tty_lines =~ /^\d+$/
80
+ result = env_tty_lines.to_i
78
81
  else
79
82
  result = TTY::System.unix? ? dynamic_height : self.default_height
80
83
  end
@@ -145,5 +148,27 @@ module TTY
145
148
  %x{tput colors 2>/dev/null}.to_i > 2
146
149
  end
147
150
 
151
+ # Find user home directory
152
+ #
153
+ # @return [String]
154
+ #
155
+ # @api public
156
+ def home
157
+ @home ||= if (env_home = ENV['HOME'])
158
+ env_home
159
+ else
160
+ begin
161
+ require 'etc'
162
+ File.expand_path("~#{Etc.getlogin}")
163
+ rescue
164
+ if TTY::System.windows?
165
+ "C:/"
166
+ else
167
+ "/"
168
+ end
169
+ end
170
+ end
171
+ end
172
+
148
173
  end # Terminal
149
174
  end # TTY
data/lib/tty/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
3
  module TTY
4
- VERSION = "0.0.5"
4
+ VERSION = "0.0.6"
5
5
  end
data/lib/tty.rb CHANGED
@@ -9,11 +9,17 @@ require 'tty/support/coercion'
9
9
  require 'tty/support/equatable'
10
10
  require 'tty/support/unicode'
11
11
 
12
- require 'tty/color'
13
12
  require 'tty/terminal'
13
+ require 'tty/terminal/color'
14
14
  require 'tty/system'
15
15
  require 'tty/table'
16
16
  require 'tty/vector'
17
+ require 'tty/shell'
18
+
19
+ require 'tty/shell/question'
20
+ require 'tty/shell/question/validation'
21
+ require 'tty/shell/question/modifier'
22
+ require 'tty/shell/statement'
17
23
 
18
24
  require 'tty/table/border'
19
25
  require 'tty/table/border/unicode'
@@ -32,6 +38,18 @@ module TTY
32
38
  # Raised when the argument type is different from expected
33
39
  class TypeError < ArgumentError; end
34
40
 
41
+ # Raised when the required argument is not supplied
42
+ class ArgumentRequired < ArgumentError; end
43
+
44
+ # Raised when the argument validation fails
45
+ class ArgumentValidation < ArgumentError; end
46
+
47
+ # Raised when the argument is not expected
48
+ class InvalidArgument < ArgumentError; end
49
+
50
+ # Raised when the passed in validation argument is of wrong type
51
+ class ValidationCoercion < TypeError; end
52
+
35
53
  class << self
36
54
 
37
55
  # Return terminal instance
@@ -0,0 +1,65 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'spec_helper'
4
+
5
+ describe TTY::Shell, '#ask' do
6
+ let(:input) { StringIO.new }
7
+ let(:output) { StringIO.new }
8
+
9
+ subject(:shell) { TTY::Shell.new(input, output) }
10
+
11
+ it 'prints message' do
12
+ shell.ask "What is your name?"
13
+ expect(output.string).to eql "What is your name?\n"
14
+ end
15
+
16
+ it 'prints an empty message ' do
17
+ shell.ask ""
18
+ expect(output.string).to eql ""
19
+ end
20
+
21
+ it 'prints an empty message and returns nil if EOF is sent to stdin' do
22
+ input << nil
23
+ input.rewind
24
+ q = shell.ask ""
25
+ expect(q.read).to eql nil
26
+ end
27
+
28
+ it 'asks a question with block' do
29
+ input << ''
30
+ input.rewind
31
+ q = shell.ask "What is your name?" do
32
+ default 'Piotr'
33
+ end
34
+ expect(q.read).to eql "Piotr"
35
+ end
36
+
37
+ context 'yes?' do
38
+ it 'agrees' do
39
+ input << 'yes'
40
+ input.rewind
41
+ expect(shell.yes?("Are you a human?")).to be_true
42
+ end
43
+
44
+ it 'disagrees' do
45
+ input << 'no'
46
+ input.rewind
47
+ expect(shell.yes?("Are you a human?")).to be_false
48
+ end
49
+ end
50
+
51
+ context 'no?' do
52
+ it 'agrees' do
53
+ input << 'no'
54
+ input.rewind
55
+ expect(shell.no?("Are you a human?")).to be_true
56
+ end
57
+
58
+ it 'disagrees' do
59
+ input << 'yes'
60
+ input.rewind
61
+ expect(shell.no?("Are you a human?")).to be_false
62
+ end
63
+ end
64
+
65
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'spec_helper'
4
+
5
+ describe TTY::Shell, '#error' do
6
+ let(:input) { StringIO.new }
7
+ let(:output) { StringIO.new }
8
+
9
+ subject(:shell) { TTY::Shell.new(input, output) }
10
+
11
+ after { output.rewind }
12
+
13
+ it 'displays one message' do
14
+ shell.error "Nothing is fine!"
15
+ expect(output.string).to eql "\e[31mNothing is fine!\e[0m\n"
16
+ end
17
+
18
+ it 'displays many messages' do
19
+ shell.error "Nothing is fine!", "All is broken!"
20
+ expect(output.string).to eql "\e[31mNothing is fine!\e[0m\n\e[31mAll is broken!\e[0m\n"
21
+ end
22
+
23
+ it 'displays message with option' do
24
+ shell.error "Nothing is fine!", :newline => false
25
+ expect(output.string).to eql "\e[31mNothing is fine!\e[0m"
26
+ end
27
+
28
+ end # error
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'spec_helper'
4
+
5
+ describe TTY::Shell, '#print_table' do
6
+ let(:input) { StringIO.new }
7
+ let(:output) { StringIO.new }
8
+ let(:header) { ['h1', 'h2'] }
9
+ let(:rows) { [['a1', 'a2'], ['b1', 'b2']] }
10
+
11
+ subject(:shell) { TTY::Shell.new(input, output) }
12
+
13
+ it 'prints a table' do
14
+ shell.print_table header, rows, :renderer => :ascii
15
+ expect(output.string).to eql <<-EOS.normalize
16
+ +--+--+
17
+ |h1|h2|
18
+ +--+--+
19
+ |a1|a2|
20
+ |b1|b2|
21
+ +--+--+\n
22
+ EOS
23
+ end
24
+
25
+ end # print_table