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

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.
@@ -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