tty 0.0.5 → 0.0.6

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/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