highline 2.0.0.pre.develop.2 → 2.0.0.pre.develop.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,48 +1,77 @@
1
1
  # coding: utf-8
2
2
 
3
3
  #--
4
- # color_scheme.rb
4
+ # originally color_scheme.rb
5
5
  #
6
6
  # Created by Richard LeBer on 2011-06-27.
7
7
  # Copyright 2011. All rights reserved
8
8
  #
9
9
  # This is Free Software. See LICENSE and COPYING for details
10
10
 
11
+
11
12
  class HighLine
12
13
 
14
+ # Creates a style using {.find_or_create_style} or
15
+ # {.find_or_create_style_list}
16
+ # @param args [Array<Style, Hash, String>] style properties
17
+ # @return [Style]
13
18
  def self.Style(*args)
14
19
  args = args.compact.flatten
15
20
  if args.size==1
16
- arg = args.first
17
- if arg.is_a?(Style)
18
- Style.list[arg.name] || Style.index(arg)
19
- elsif arg.is_a?(::String) && arg =~ /^\e\[/ # arg is a code
20
- if styles = Style.code_index[arg]
21
- styles.first
22
- else
23
- Style.new(:code=>arg)
24
- end
25
- elsif style = Style.list[arg]
26
- style
27
- elsif HighLine.color_scheme && HighLine.color_scheme[arg]
28
- HighLine.color_scheme[arg]
29
- elsif arg.is_a?(Hash)
30
- Style.new(arg)
31
- elsif arg.to_s.downcase =~ /^rgb_([a-f0-9]{6})$/
32
- Style.rgb($1)
33
- elsif arg.to_s.downcase =~ /^on_rgb_([a-f0-9]{6})$/
34
- Style.rgb($1).on
21
+ find_or_create_style(args.first)
22
+ else
23
+ find_or_create_style_list(*args)
24
+ end
25
+ end
26
+
27
+ # Search for a Style with the given properties and return it.
28
+ # If there's no matched Style, it creates one.
29
+ # You can pass a Style, String or a Hash.
30
+ # @param arg [Style, String, Hash] style properties
31
+ # @return [Style] found or creted Style
32
+ def self.find_or_create_style(arg)
33
+ if arg.is_a?(Style)
34
+ Style.list[arg.name] || Style.index(arg)
35
+ elsif arg.is_a?(::String) && arg =~ /^\e\[/ # arg is a code
36
+ if styles = Style.code_index[arg]
37
+ styles.first
35
38
  else
36
- raise NameError, "#{arg.inspect} is not a defined Style"
39
+ Style.new(:code=>arg)
37
40
  end
41
+ elsif style = Style.list[arg]
42
+ style
43
+ elsif HighLine.color_scheme && HighLine.color_scheme[arg]
44
+ HighLine.color_scheme[arg]
45
+ elsif arg.is_a?(Hash)
46
+ Style.new(arg)
47
+ elsif arg.to_s.downcase =~ /^rgb_([a-f0-9]{6})$/
48
+ Style.rgb($1)
49
+ elsif arg.to_s.downcase =~ /^on_rgb_([a-f0-9]{6})$/
50
+ Style.rgb($1).on
38
51
  else
39
- name = args
40
- Style.list[name] || Style.new(:list=>args)
52
+ raise NameError, "#{arg.inspect} is not a defined Style"
41
53
  end
42
54
  end
43
55
 
56
+ # Find a Style list or create a new one.
57
+ # @param args [Array<Symbol>] an Array of Symbols of each style
58
+ # that will be on the style list.
59
+ # @return [Style] Style list
60
+ # @example Creating a Style list of the combined RED and BOLD styles.
61
+ # style_list = HighLine.find_or_create_style_list(:red, :bold)
62
+
63
+ def self.find_or_create_style_list(*args)
64
+ name = args
65
+ Style.list[name] || Style.new(:list=>args)
66
+ end
67
+
68
+ # ANSI styles to be used by HighLine.
44
69
  class Style
45
70
 
71
+ # Index the given style.
72
+ # Uses @code_index (Hash) as repository.
73
+ # @param style [Style]
74
+ # @return [Style] the given style
46
75
  def self.index(style)
47
76
  if style.name
48
77
  @styles ||= {}
@@ -57,6 +86,9 @@ class HighLine
57
86
  style
58
87
  end
59
88
 
89
+ # Clear all custom Styles, restoring the Style index to
90
+ # builtin styles only.
91
+ # @return [void]
60
92
  def self.clear_index
61
93
  # reset to builtin only styles
62
94
  @styles = list.select { |name, style| style.builtin }
@@ -64,16 +96,40 @@ class HighLine
64
96
  @styles.each { |name, style| index(style) }
65
97
  end
66
98
 
99
+ # Converts all given color codes to hexadecimal and
100
+ # join them in a single string. If any given color code
101
+ # is already a String, doesn't perform any convertion.
102
+ #
103
+ # @param colors [Array<Numeric, String>] color codes
104
+ # @return [String] all color codes joined
105
+ # @example
106
+ # HighLine::Style.rgb_hex(9, 10, "11") # => "090a11"
67
107
  def self.rgb_hex(*colors)
68
108
  colors.map do |color|
69
109
  color.is_a?(Numeric) ? '%02x'%color : color.to_s
70
110
  end.join
71
111
  end
72
112
 
113
+ # Split an rgb code string into its 3 numerical compounds.
114
+ # @param hex [String] rgb code string like "010F0F"
115
+ # @return [Array<Numeric>] numerical compounds like [1, 15, 15]
116
+ # @example
117
+ # HighLine::Style.rgb_parts("090A0B") # => [9, 10, 11]
118
+
73
119
  def self.rgb_parts(hex)
74
120
  hex.scan(/../).map{|part| part.to_i(16)}
75
121
  end
76
122
 
123
+ # Search for or create a new Style from the colors provided.
124
+ # @param colors (see .rgb_hex)
125
+ # @return [Style] a Style with the rgb colors provided.
126
+ # @example Creating a new Style based on rgb code
127
+ # rgb_style = HighLine::Style.rgb(9, 10, 11)
128
+ #
129
+ # rgb_style.name # => :rgb_090a0b
130
+ # rgb_style.code # => "\e[38;5;16m"
131
+ # rgb_style.rgb # => [9, 10, 11]
132
+ #
77
133
  def self.rgb(*colors)
78
134
  hex = rgb_hex(*colors)
79
135
  name = ('rgb_' + hex).to_sym
@@ -85,34 +141,58 @@ class HighLine
85
141
  end
86
142
  end
87
143
 
144
+ # Returns the rgb number to be used as escape code on ANSI terminals.
145
+ # @param parts [Array<Numeric>] three numerical codes for red, green and blue
146
+ # @return [Numeric] to be used as escape code on ANSI terminals
88
147
  def self.rgb_number(*parts)
89
148
  parts = parts.flatten
90
149
  16 + parts.inject(0) {|kode, part| kode*6 + (part/256.0*6.0).floor}
91
150
  end
92
151
 
152
+ # From an ANSI number (color escape code), craft an 'rgb_hex' code of it
153
+ # @param ansi_number [Integer] ANSI escape code
154
+ # @return [String] all color codes joined as {.rgb_hex}
93
155
  def self.ansi_rgb_to_hex(ansi_number)
94
156
  raise "Invalid ANSI rgb code #{ansi_number}" unless (16..231).include?(ansi_number)
95
157
  parts = (ansi_number-16).to_s(6).rjust(3,'0').scan(/./).map{|d| (d.to_i*255.0/6.0).ceil}
96
158
  rgb_hex(*parts)
97
159
  end
98
160
 
161
+ # @return [Hash] list of all cached Styles
99
162
  def self.list
100
163
  @styles ||= {}
101
164
  end
102
165
 
166
+ # @return [Hash] list of all cached Style codes
103
167
  def self.code_index
104
168
  @code_index ||= {}
105
169
  end
106
170
 
171
+ # Remove any ANSI color escape sequence of the given String.
172
+ # @param string [String]
173
+ # @return [String]
107
174
  def self.uncolor(string)
108
175
  string.gsub(/\e\[\d+(;\d+)*m/, '')
109
176
  end
110
177
 
111
- attr_reader :name, :list
112
- attr_accessor :rgb, :builtin
178
+ # Style name
179
+ # @return [Symbol] the name of the Style
180
+ attr_reader :name
181
+
182
+ # When a compound Style, returns a list of its components.
183
+ # @return [Array<Symbol>] components of a Style list
184
+ attr_reader :list
185
+
186
+ # @return [Array] the three numerical rgb codes. Ex: [10, 12, 127]
187
+ attr_accessor :rgb
188
+
189
+ # @return [Boolean] true if the Style is builtin or not.
190
+ attr_accessor :builtin
113
191
 
114
192
  # Single color/styles have :name, :code, :rgb (possibly), :builtin
115
193
  # Compound styles have :name, :list, :builtin
194
+ #
195
+ # @param defn [Hash] options for the Style to be created.
116
196
  def initialize(defn = {})
117
197
  @definition = defn
118
198
  @name = defn[:name]
@@ -129,18 +209,27 @@ class HighLine
129
209
  self.class.index self unless defn[:no_index]
130
210
  end
131
211
 
212
+ # Duplicate current Style using the same definition used to create it.
213
+ # @return [Style] duplicated Style
132
214
  def dup
133
215
  self.class.new(@definition)
134
216
  end
135
217
 
218
+ # @return [Hash] the definition used to create this Style
136
219
  def to_hash
137
220
  @definition
138
221
  end
139
222
 
223
+ # Uses the Style definition to add ANSI color escape codes
224
+ # to a a given String
225
+ # @param string [String] to be colored
226
+ # @return [String] an ANSI colored string
140
227
  def color(string)
141
228
  code + string + HighLine::CLEAR
142
229
  end
143
230
 
231
+ # @return [String] all codes of the Style list joined together (if a Style list)
232
+ # @return [String] the Style code
144
233
  def code
145
234
  if @list
146
235
  @list.map{|element| HighLine.Style(element).code}.join
@@ -149,18 +238,25 @@ class HighLine
149
238
  end
150
239
  end
151
240
 
241
+ # @return [Integer] the RED component of the rgb code
152
242
  def red
153
243
  @rgb && @rgb[0]
154
244
  end
155
245
 
246
+ # @return [Integer] the GREEN component of the rgb code
156
247
  def green
157
248
  @rgb && @rgb[1]
158
249
  end
159
250
 
251
+ # @return [Integer] the BLUE component of the rgb code
160
252
  def blue
161
253
  @rgb && @rgb[2]
162
254
  end
163
255
 
256
+ # Duplicate Style with some minor changes
257
+ # @param new_name [Symbol]
258
+ # @param options [Hash] Style attributes to be changed
259
+ # @return [Style] new Style with changed attributes
164
260
  def variant(new_name, options={})
165
261
  raise "Cannot create a variant of a style list (#{inspect})" if @list
166
262
  new_code = options[:code] || code
@@ -172,15 +268,19 @@ class HighLine
172
268
  self.class.new(self.to_hash.merge(:name=>new_name, :code=>new_code, :rgb=>new_rgb))
173
269
  end
174
270
 
271
+ # Uses the color as background and return a new style.
272
+ # @return [Style]
175
273
  def on
176
274
  new_name = ('on_'+@name.to_s).to_sym
177
275
  self.class.list[new_name] ||= variant(new_name, :increment=>10)
178
276
  end
179
277
 
278
+ # @return [Style] a brighter version of this Style
180
279
  def bright
181
280
  create_bright_variant(:bright)
182
281
  end
183
282
 
283
+ # @return [Style] a lighter version of this Style
184
284
  def light
185
285
  create_bright_variant(:light)
186
286
  end
@@ -3,13 +3,29 @@
3
3
  require 'forwardable'
4
4
 
5
5
  class HighLine
6
+ # Renders an erb template taking a {Question} and a {HighLine} instance
7
+ # as context.
6
8
  class TemplateRenderer
7
9
  extend Forwardable
8
10
 
9
11
  def_delegators :@highline, :color, :list, :key
10
12
  def_delegators :@source, :answer_type, :prompt, :header, :answer
11
13
 
12
- attr_reader :template, :source, :highline
14
+ # @return [ERB] ERB template being rendered
15
+ attr_reader :template
16
+
17
+ # @return [Question, Menu] Question instance used as context
18
+ attr_reader :source
19
+
20
+ # @return [HighLine] HighLine instance used as context
21
+ attr_reader :highline
22
+
23
+ # Initializes the TemplateRenderer object with its template and
24
+ # HighLine and Question contexts.
25
+ #
26
+ # @param template [ERB] ERB template.
27
+ # @param source [Question] question object.
28
+ # @param highline [HighLine] HighLine instance.
13
29
 
14
30
  def initialize(template, source, highline)
15
31
  @template = template
@@ -17,20 +33,28 @@ class HighLine
17
33
  @highline = highline
18
34
  end
19
35
 
36
+ # @return [String] rendered template
20
37
  def render
21
38
  template.result(binding)
22
39
  end
23
40
 
41
+ # Returns an error message when the called method
42
+ # is not available.
43
+ # @return [String] error message.
24
44
  def method_missing(method, *args)
25
45
  "Method #{method} with args #{args.inspect} " +
26
46
  "is not available on #{self.inspect}. " +
27
47
  "Try #{methods(false).sort.inspect}"
28
48
  end
29
49
 
50
+ # @return [Question, Menu] {#source} attribute.
30
51
  def menu
31
52
  source
32
53
  end
33
54
 
55
+ # If some constant is missing at this TemplateRenderer instance,
56
+ # get it from HighLine. Useful to get color and style contants.
57
+ # @param name [Symbol] automatically passed constant's name as Symbol
34
58
  def self.const_missing(name)
35
59
  HighLine.const_get(name)
36
60
  end
@@ -12,7 +12,14 @@
12
12
  require "highline/compatibility"
13
13
 
14
14
  class HighLine
15
+ # Basic Terminal class which HighLine will direct
16
+ # input and output to.
17
+ # The specialized Terminals all decend from this HighLine::Terminal class
15
18
  class Terminal
19
+
20
+ # Probe for and return a suitable Terminal instance
21
+ # @param input [IO] input stream
22
+ # @param output [IO] output stream
16
23
  def self.get_terminal(input, output)
17
24
  terminal = nil
18
25
 
@@ -34,22 +41,37 @@ class HighLine
34
41
  terminal
35
42
  end
36
43
 
37
- attr_reader :input, :output
44
+ # @return [IO] input stream
45
+ attr_reader :input
46
+
47
+ # @return [IO] output stream
48
+ attr_reader :output
38
49
 
50
+ # Creates a terminal instance based on given input and output streams.
51
+ # @param input [IO] input stream
52
+ # @param output [IO] output stream
39
53
  def initialize(input, output)
40
54
  @input = input
41
55
  @output = output
42
56
  end
43
57
 
58
+ # An initialization callback.
59
+ # It is called by {.get_terminal}.
44
60
  def initialize_system_extensions
45
61
  end
46
62
 
63
+ # @return [Array<Integer, Integer>] two value terminal
64
+ # size like [columns, lines]
47
65
  def terminal_size
66
+ [80, 24]
48
67
  end
49
68
 
69
+ # Enter Raw No Echo mode.
50
70
  def raw_no_echo_mode
51
71
  end
52
72
 
73
+ # Yieds a block to be executed in Raw No Echo mode and
74
+ # then restore the terminal state.
53
75
  def raw_no_echo_mode_exec
54
76
  raw_no_echo_mode
55
77
  yield
@@ -57,22 +79,123 @@ class HighLine
57
79
  restore_mode
58
80
  end
59
81
 
82
+ # Restore terminal to its default mode
60
83
  def restore_mode
61
84
  end
62
85
 
86
+ # Get one character from the terminal
87
+ # @return [String] one character
63
88
  def get_character
64
89
  end
65
90
 
91
+ # Get one line from the terminal and format accordling.
92
+ # Use readline if question has readline mode set.
93
+ # @param question [HighLine::Question]
94
+ # @param highline [HighLine]
95
+ # @param options [Hash]
96
+ def get_line(question, highline, options={})
97
+ raw_answer =
98
+ if question.readline
99
+ get_line_with_readline(question, highline, options={})
100
+ else
101
+ get_line_default(highline)
102
+ end
103
+
104
+ question.format_answer(raw_answer)
105
+ end
106
+
107
+ # Get one line using #readline_read
108
+ # @param (see #get_line)
109
+ def get_line_with_readline(question, highline, options={})
110
+ require "readline" # load only if needed
111
+
112
+ question_string = highline.render_statement(question)
113
+
114
+ raw_answer = readline_read(question_string, question)
115
+
116
+ if !raw_answer and highline.track_eof?
117
+ raise EOFError, "The input stream is exhausted."
118
+ end
119
+
120
+ raw_answer || ""
121
+ end
122
+
123
+ # Use readline to read one line
124
+ # @param prompt [String] Readline prompt
125
+ # @param question [HighLine::Question] question from where to get
126
+ # autocomplete candidate strings
127
+ def readline_read(prompt, question)
128
+ # prep auto-completion
129
+ unless question.selection.empty?
130
+ Readline.completion_proc = lambda do |str|
131
+ question.selection.grep(/\A#{Regexp.escape(str)}/)
132
+ end
133
+ end
134
+
135
+ # work-around ugly readline() warnings
136
+ old_verbose = $VERBOSE
137
+ $VERBOSE = nil
138
+
139
+ raw_answer = run_preserving_stty do
140
+ Readline.readline(prompt, true)
141
+ end
142
+
143
+ $VERBOSE = old_verbose
144
+
145
+ raw_answer
146
+ end
147
+
148
+ # Get one line from terminal using default #gets method.
149
+ # @param highline (see #get_line)
150
+ def get_line_default(highline)
151
+ raise EOFError, "The input stream is exhausted." if highline.track_eof? and
152
+ highline.input.eof?
153
+ highline.input.gets
154
+ end
155
+
156
+ # @!group Enviroment queries
157
+
158
+ # Running on JRuby?
66
159
  def jruby?
67
160
  defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
68
161
  end
69
162
 
163
+ # Running on Rubinius?
70
164
  def rubinius?
71
165
  defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
72
166
  end
73
167
 
168
+ # Running on Windows?
74
169
  def windows?
75
170
  defined?(RUBY_PLATFORM) && (RUBY_PLATFORM =~ /mswin|mingw|cygwin/)
76
171
  end
172
+
173
+ # @!endgroup
174
+
175
+ # Returns the class name as String. Useful for debuggin.
176
+ # @return [String] class name. Ex: "HighLine::Terminal::IOConsole"
177
+ def character_mode
178
+ self.class.name
179
+ end
180
+
181
+ private
182
+
183
+ # Yield a block using stty shell commands to preserve the terminal state.
184
+ def run_preserving_stty
185
+ save_stty
186
+ yield
187
+ ensure
188
+ restore_stty
189
+ end
190
+
191
+ # Saves terminal state using shell stty command.
192
+ def save_stty
193
+ @stty_save = `stty -g`.chomp rescue nil
194
+ end
195
+
196
+ # Restores terminal state using shell stty command.
197
+ def restore_stty
198
+ system("stty", @stty_save) if @stty_save
199
+ end
77
200
  end
78
201
  end