summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAbinoam P. Marques Jr. <abinoam@gmail.com>2015-07-06 19:14:22 -0300
committerAbinoam P. Marques Jr. <abinoam@gmail.com>2015-07-06 19:14:22 -0300
commitbb9902245d4a719c191e4e01d7e9a5a9e53d38ee (patch)
treea6c353544c203e4e1ccf4b33436c6fae8d3d6fcc
parent22e4fdfaee6d4d2ccbfa318fc4083cde05280843 (diff)
parent88978cd2ff5da525f4feb81abcf32767e40790fa (diff)
downloadhighline-v2.0.0-develop.tar.gz
Merge pull request #140 from abinoam/feat_refac_askv2.0.0-develop
Develop Release 2.0.0-develop.1
-rw-r--r--.gitignore2
-rw-r--r--.travis.yml2
-rw-r--r--Changelog.md66
-rw-r--r--Gemfile13
-rw-r--r--README.rdoc1
-rw-r--r--examples/repeat_entry.rb2
-rwxr-xr-xlib/highline.rb850
-rw-r--r--lib/highline/color_scheme.rb2
-rw-r--r--lib/highline/compatibility.rb2
-rw-r--r--lib/highline/import.rb2
-rw-r--r--lib/highline/list.rb93
-rw-r--r--lib/highline/list_renderer.rb232
-rw-r--r--lib/highline/menu.rb29
-rw-r--r--lib/highline/paginator.rb43
-rwxr-xr-xlib/highline/question.rb206
-rw-r--r--lib/highline/question/answer_converter.rb84
-rw-r--r--lib/highline/simulate.rb2
-rw-r--r--lib/highline/statement.rb58
-rw-r--r--lib/highline/string_extensions.rb2
-rwxr-xr-xlib/highline/style.rb2
-rwxr-xr-xlib/highline/system_extensions.rb251
-rw-r--r--lib/highline/template_renderer.rb38
-rwxr-xr-xlib/highline/terminal.rb50
-rw-r--r--lib/highline/terminal/jruby.rb37
-rw-r--r--lib/highline/terminal/jruby_jline.rb35
-rw-r--r--lib/highline/terminal/ncurses.rb33
-rw-r--r--lib/highline/terminal/stty.rb19
-rw-r--r--lib/highline/terminal/unix_stty.rb93
-rw-r--r--lib/highline/terminal/unix_termios.rb23
-rw-r--r--lib/highline/terminal/windows.rb38
-rw-r--r--lib/highline/terminal/windows_dl_import.rb37
-rw-r--r--lib/highline/terminal/windows_fiddle.rb32
-rw-r--r--lib/highline/version.rb4
-rw-r--r--lib/highline/wrapper.rb43
-rw-r--r--test/string_methods.rb3
-rw-r--r--test/test_color_scheme.rb4
-rw-r--r--test/test_helper.rb11
-rwxr-xr-xtest/test_highline.rb129
-rw-r--r--test/test_import.rb4
-rw-r--r--test/test_list.rb61
-rw-r--r--test/test_menu.rb5
-rw-r--r--test/test_paginator.rb31
-rw-r--r--test/test_simulator.rb25
-rw-r--r--test/test_string_extension.rb4
-rw-r--r--test/test_string_highline.rb4
-rwxr-xr-xtest/test_style.rb4
-rw-r--r--test/test_wrapper.rb189
47 files changed, 1905 insertions, 995 deletions
diff --git a/.gitignore b/.gitignore
index de31a45..a49838e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
doc
pkg
+.DS_Store
+coverage
diff --git a/.travis.yml b/.travis.yml
index 87c2844..f4ee366 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,6 +7,8 @@ rvm:
- 2.0.0
- 2.1.5
- 2.2.0
+ - 2.2.1
+ - 2.2.2
- rbx-2
notifications:
email: false
diff --git a/Changelog.md b/Changelog.md
index 9a58217..b690369 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -2,6 +2,72 @@
Below is a complete listing of changes for each revision of HighLine.
+### 2.0.0-develop.1 / 2015-06-11
+
+This is the first development version of the 2.0.0 series. It's the begining of a refactoring phase on HighLine development cycle.
+
+#### SOME HISTORY
+
+In 2014 I emailed James Edward Gray II (@JEG2) about HighLine. One of his ideas was to completely refactor the library so that it could be easier to reuse and improve it. I've began my contributions to HighLine trying to fix some of the open issues at that time so that we could "freeze" a stable version of HighLine that people could rely on. Then I've began to study HighLine source code with James' help and started to refactor some parts of the code. Abinoam P. Marques Jr. (@abinoam)
+
+#### NOTES
+
+* This release differs from current master branch by more than 180 commits.
+* The main changes will be only summarized bellow (as there are many, and a detailed description of each is not productive).
+* You could try `git log -p` to see all of them.
+* During the last commits, all possible efforts were taken to preserve the tests passing status.
+* 100% test passing gives you no guarantee that this new version will work for you. This happens for many reasons. One of them is that we don't currently have 100% test coverage.
+* So, this version is not suitable for use in production.
+* [Metric_fu](https://github.com/metricfu/metric_fu) and [Code Climate](https://codeclimate.com/github/abinoam/highline) were used here not to strictly "guide" what should be changed, but to have some way to objectively measure the progresses made so far.
+
+#### CHANGES SUMMARY
+* Extracted a lot of smaller methods from bigger ones
+* Extracted smaller classes/modules from bigger ones, so they could be self contained with less external dependencies as possible, for example:
+ * HighLine::Statement
+ * HighLine::List
+ * HighLine::ListRenderer
+ * HighLine::TemplateRenderer
+ * HighLine::Question::AnswerConverter
+ * HighLine::Terminal
+ * HighLine::Terminal::UnixStty
+ * HighLine::Paginator
+ * HighLine::Wrapper
+* After extracting each class/module some refactoring were applied to them lowering code complexity
+
+#### METRICS SUMMARY
+Some of the metrics used to track progress are summarized bellow. Some of them have got a lot better as Flay, Flog and Reek, others like Cane haven't (probably because we didn't commented out the new code yet)
+
+__CODECLIMATE__
+
+* GPA: 3.60 -> 3.67 (higher is better)
+
+__CANE__ - reports code quality threshold violations (lower is better)
+
+* Total 92 -> 105
+ * Methods exceeding allowed Abc complexity: 14 -> 10
+ * Lines violating style requirements: 69 -> 72
+ * Class definitions requiring comments: 9 -> 23
+
+__FLAY__ - analyzes ruby code for structural similarities (code duplication - lower is better)
+
+* Total: 490 -> 94
+
+__FLOG__ - measures code complexity (lower is better)
+
+* Top 5% average: 127.9458 -> 40.99812
+* Average: 17.37982 -> 7.663875
+* Total: 2158.5 -> 1969.6
+
+__REEK__ - detects common code smells in ruby code (lower is better)
+
+* DuplicateMethodCall: 144 -> 54
+* TooManyStatements: 26 -> 30
+
+### 1.7.2 / 2015-04-19
+
+#### Bug fixes
+* Fix #138 (a regression of #131). PR #139.
+
### 1.7.1 / 2015-02-24
#### Enhancements
diff --git a/Gemfile b/Gemfile
index d720f61..820bc6d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,9 +1,12 @@
source "https://rubygems.org"
-gem "rake", :require => false
-gem "rdoc", :require => false
+gem "rake", require: false
+gem "rdoc", require: false
-group(:development, :tests) do
- gem "code_statistics", :require => false
- gem "minitest", :require => false
+group :development, :test do
+ gem "code_statistics", require: false
+ gem "minitest", require: false
end
+
+gem "codeclimate-test-reporter", group: :test, require: false
+gem "simplecov", group: :test, require: false \ No newline at end of file
diff --git a/README.rdoc b/README.rdoc
index cbf9ee9..934614b 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -3,6 +3,7 @@
by James Edward Gray II
{<img src="https://travis-ci.org/JEG2/highline.svg" alt="Build Status" />}[https://travis-ci.org/JEG2/highline]
+{<img src="https://img.shields.io/gem/v/highline.svg?style=flat" />}[http://rubygems.org/gems/highline]
== Description
diff --git a/examples/repeat_entry.rb b/examples/repeat_entry.rb
index 012e960..753262e 100644
--- a/examples/repeat_entry.rb
+++ b/examples/repeat_entry.rb
@@ -11,7 +11,7 @@ end
puts "Ok, you did it."
-pass = ask("<%= @key %>: ") do |q|
+pass = ask("<%= key %>: ") do |q|
q.echo = '*'
q.verify_match = true
q.gather = {"Enter a password" => '',
diff --git a/lib/highline.rb b/lib/highline.rb
index 4528d44..2a917c2 100755
--- a/lib/highline.rb
+++ b/lib/highline.rb
@@ -1,4 +1,5 @@
# coding: utf-8
+
# highline.rb
#
# Created by James Edward Gray II on 2005-04-26.
@@ -12,12 +13,14 @@ require "erb"
require "optparse"
require "stringio"
require "abbrev"
-require "highline/system_extensions"
+require "highline/terminal"
require "highline/question"
require "highline/menu"
require "highline/color_scheme"
require "highline/style"
require "highline/version"
+require "highline/statement"
+require "highline/list_renderer"
#
# A HighLine object is a "high-level line oriented" shell over an input and an
@@ -34,6 +37,18 @@ class HighLine
# do nothing, just creating a unique error type
end
+ class NotValidQuestionError < QuestionError
+ # do nothing, just creating a unique error type
+ end
+
+ class NotInRangeQuestionError < QuestionError
+ # do nothing, just creating a unique error type
+ end
+
+ class NoConfirmationQuestionError < QuestionError
+ # do nothing, just creating a unique error type
+ end
+
# The setting used to disable color output.
@@use_color = true
@@ -67,6 +82,10 @@ class HighLine
@@track_eof
end
+ def track_eof?
+ self.class.track_eof?
+ end
+
# The setting used to control color schemes.
@@color_scheme = nil
@@ -201,19 +220,13 @@ class HighLine
self.page_at = page_at
@question = nil
- @answer = nil
- @menu = nil
@header = nil
@prompt = nil
- @gather = nil
- @answers = nil
@key = nil
- initialize_system_extensions if respond_to?(:initialize_system_extensions)
+ @terminal = HighLine::Terminal.get_terminal
end
- include HighLine::SystemExtensions
-
# The current column setting for wrapping output.
attr_reader :wrap_at
# The current row setting for paging output.
@@ -225,6 +238,17 @@ class HighLine
# The indentation level
attr_accessor :indent_level
+ attr_reader :input, :output
+
+ attr_reader :key
+
+ attr_reader :question
+
+ # System specific that responds to #initialize_system_extensions,
+ # #terminal_size, #raw_no_echo_mode, #restore_mode, #get_character.
+ # It polymorphically handles specific cases for different platforms.
+ attr_reader :terminal
+
#
# A shortcut to HighLine.ask() a question that only accepts "yes" or "no"
# answers ("y" and "n" are allowed) and returns +true+ or +false+
@@ -259,70 +283,14 @@ class HighLine
#
# Raises EOFError if input is exhausted.
#
- def ask( question, answer_type = nil, &details ) # :yields: question
- @question ||= Question.new(question, answer_type, &details)
-
- return gather if @question.gather
-
- # readline() needs to handle its own output, but readline only supports
- # full line reading. Therefore if @question.echo is anything but true,
- # the prompt will not be issued. And we have to account for that now.
- # Also, JRuby-1.7's ConsoleReader.readLine() needs to be passed the prompt
- # to handle line editing properly.
- say(@question) unless ((JRUBY or @question.readline) and (@question.echo == true and @question.limit.nil?))
-
- begin
- @answer = @question.answer_or_default(get_response)
- unless @question.valid_answer?(@answer)
- explain_error(:not_valid)
- raise QuestionError
- end
-
- @answer = @question.convert(@answer)
-
- if @question.in_range?(@answer)
- if @question.confirm
- # need to add a layer of scope to ask a question inside a
- # question, without destroying instance data
- context_change = self.class.new(@input, @output, @wrap_at, @page_at, @indent_size, @indent_level)
- if @question.confirm == true
- confirm_question = "Are you sure? "
- else
- # evaluate ERb under initial scope, so it will have
- # access to @question and @answer
- template = ERB.new(@question.confirm, nil, "%")
- confirm_question = template.result(binding)
- end
- unless context_change.agree(confirm_question)
- explain_error(nil)
- raise QuestionError
- end
- end
-
- @answer
- else
- explain_error(:not_in_range)
- raise QuestionError
- end
- rescue QuestionError
- retry
- rescue ArgumentError, NameError => error
- raise if error.is_a?(NoMethodError)
- if error.message =~ /ambiguous/
- # the assumption here is that OptionParser::Completion#complete
- # (used for ambiguity resolution) throws exceptions containing
- # the word 'ambiguous' whenever resolution fails
- explain_error(:ambiguous_completion)
- else
- explain_error(:invalid_type)
- end
- retry
- rescue Question::NoAutoCompleteMatch
- explain_error(:no_completion)
- retry
- ensure
- @question = nil # Reset Question object.
+ def ask(template_or_question, answer_type = nil, options = {}, &details) # :yields: question
+ if template_or_question.is_a? Question
+ @question = template_or_question
+ else
+ @question = Question.new(template_or_question, answer_type, &details)
end
+
+ return question.ask_at(self)
end
#
@@ -341,40 +309,34 @@ class HighLine
# Raises EOFError if input is exhausted.
#
def choose( *items, &details )
- @menu = @question = Menu.new(&details)
- @menu.choices(*items) unless items.empty?
+ menu = Menu.new(&details)
+ menu.choices(*items) unless items.empty?
# Set auto-completion
- @menu.completion = @menu.options
- # Set _answer_type_ so we can double as the Question for ask().
- @menu.answer_type = if @menu.shell
- lambda do |command| # shell-style selection
- first_word = command.to_s.split.first || ""
+ menu.completion = menu.options
- options = @menu.options
- options.extend(OptionParser::Completion)
- answer = options.complete(first_word)
+ shell_style_lambda = lambda do |command| # shell-style selection
+ first_word = command.to_s.split.first || ""
- if answer.nil?
- raise Question::NoAutoCompleteMatch
- end
+ options = menu.options
+ options.extend(OptionParser::Completion)
+ answer = options.complete(first_word)
- [answer.last, command.sub(/^\s*#{first_word}\s*/, "")]
- end
- else
- @menu.options # normal menu selection, by index or name
+ raise Question::NoAutoCompleteMatch unless answer
+
+ [answer.last, command.sub(/^\s*#{first_word}\s*/, "")]
end
- # Provide hooks for ERb layouts.
- @header = @menu.header
- @prompt = @menu.prompt
+ # Set _answer_type_ so we can double as the Question for ask().
+ # menu.option = normal menu selection, by index or name
+ menu.answer_type = menu.shell ? shell_style_lambda : menu.options
+
+ selected = ask(menu)
- if @menu.shell
- selected = ask("Ignored", @menu.answer_type)
- @menu.select(self, *selected)
+ if menu.shell
+ menu.select(self, *selected)
else
- selected = ask("Ignored", @menu.answer_type)
- @menu.select(self, selected)
+ menu.select(self, selected)
end
end
@@ -419,203 +381,8 @@ class HighLine
self.class.uncolor(string)
end
- #
- # This method is a utility for quickly and easily laying out lists. It can
- # be accessed within ERb replacements of any text that will be sent to the
- # user.
- #
- # The only required parameter is _items_, which should be the Array of items
- # to list. A specified _mode_ controls how that list is formed and _option_
- # has different effects, depending on the _mode_. Recognized modes are:
- #
- # <tt>:columns_across</tt>:: _items_ will be placed in columns,
- # flowing from left to right. If given,
- # _option_ is the number of columns to be
- # used. When absent, columns will be
- # determined based on _wrap_at_ or a
- # default of 80 characters.
- # <tt>:columns_down</tt>:: Identical to <tt>:columns_across</tt>,
- # save flow goes down.
- # <tt>:uneven_columns_across</tt>:: Like <tt>:columns_across</tt> but each
- # column is sized independently.
- # <tt>:uneven_columns_down</tt>:: Like <tt>:columns_down</tt> but each
- # column is sized independently.
- # <tt>:inline</tt>:: All _items_ are placed on a single line.
- # The last two _items_ are separated by
- # _option_ or a default of " or ". All
- # other _items_ are separated by ", ".
- # <tt>:rows</tt>:: The default mode. Each of the _items_ is
- # placed on its own line. The _option_
- # parameter is ignored in this mode.
- #
- # Each member of the _items_ Array is passed through ERb and thus can contain
- # their own expansions. Color escape expansions do not contribute to the
- # final field width.
- #
- def list( items, mode = :rows, option = nil )
- items = items.to_ary.map do |item|
- if item.nil?
- ""
- else
- ERB.new(item, nil, "%").result(binding)
- end
- end
-
- if items.empty?
- ""
- else
- case mode
- when :inline
- option = " or " if option.nil?
-
- if items.size == 1
- items.first
- else
- items[0..-2].join(", ") + "#{option}#{items.last}"
- end
- when :columns_across, :columns_down
- max_length = actual_length(
- items.max { |a, b| actual_length(a) <=> actual_length(b) }
- )
-
- if option.nil?
- limit = @wrap_at || 80
- option = (limit + 2) / (max_length + 2)
- end
-
- items = items.map do |item|
- pad = max_length + (item.to_s.length - actual_length(item))
- "%-#{pad}s" % item
- end
- row_count = (items.size / option.to_f).ceil
-
- if mode == :columns_across
- rows = Array.new(row_count) { Array.new }
- items.each_with_index do |item, index|
- rows[index / option] << item
- end
-
- rows.map { |row| row.join(" ") + "\n" }.join
- else
- columns = Array.new(option) { Array.new }
- items.each_with_index do |item, index|
- columns[index / row_count] << item
- end
-
- list = ""
- columns.first.size.times do |index|
- list << columns.map { |column| column[index] }.
- compact.join(" ") + "\n"
- end
- list
- end
- when :uneven_columns_across
- if option.nil?
- limit = @wrap_at || 80
- items.size.downto(1) do |column_count|
- row_count = (items.size / column_count.to_f).ceil
- rows = Array.new(row_count) { Array.new }
- items.each_with_index do |item, index|
- rows[index / column_count] << item
- end
-
- widths = Array.new(column_count, 0)
- rows.each do |row|
- row.each_with_index do |field, column|
- size = actual_length(field)
- widths[column] = size if size > widths[column]
- end
- end
-
- if column_count == 1 or
- widths.inject(0) { |sum, n| sum + n + 2 } <= limit + 2
- return rows.map { |row|
- row.zip(widths).map { |field, i|
- "%-#{i + (field.to_s.length - actual_length(field))}s" % field
- }.join(" ") + "\n"
- }.join
- end
- end
- else
- row_count = (items.size / option.to_f).ceil
- rows = Array.new(row_count) { Array.new }
- items.each_with_index do |item, index|
- rows[index / option] << item
- end
-
- widths = Array.new(option, 0)
- rows.each do |row|
- row.each_with_index do |field, column|
- size = actual_length(field)
- widths[column] = size if size > widths[column]
- end
- end
-
- return rows.map { |row|
- row.zip(widths).map { |field, i|
- "%-#{i + (field.to_s.length - actual_length(field))}s" % field
- }.join(" ") + "\n"
- }.join
- end
- when :uneven_columns_down
- if option.nil?
- limit = @wrap_at || 80
- items.size.downto(1) do |column_count|
- row_count = (items.size / column_count.to_f).ceil
- columns = Array.new(column_count) { Array.new }
- items.each_with_index do |item, index|
- columns[index / row_count] << item
- end
-
- widths = Array.new(column_count, 0)
- columns.each_with_index do |column, i|
- column.each do |field|
- size = actual_length(field)
- widths[i] = size if size > widths[i]
- end
- end
-
- if column_count == 1 or
- widths.inject(0) { |sum, n| sum + n + 2 } <= limit + 2
- list = ""
- columns.first.size.times do |index|
- list << columns.zip(widths).map { |column, width|
- field = column[index]
- "%-#{width + (field.to_s.length - actual_length(field))}s" %
- field
- }.compact.join(" ").strip + "\n"
- end
- return list
- end
- end
- else
- row_count = (items.size / option.to_f).ceil
- columns = Array.new(option) { Array.new }
- items.each_with_index do |item, index|
- columns[index / row_count] << item
- end
-
- widths = Array.new(option, 0)
- columns.each_with_index do |column, i|
- column.each do |field|
- size = actual_length(field)
- widths[i] = size if size > widths[i]
- end
- end
-
- list = ""
- columns.first.size.times do |index|
- list << columns.zip(widths).map { |column, width|
- field = column[index]
- "%-#{width + (field.to_s.length - actual_length(field))}s" % field
- }.compact.join(" ").strip + "\n"
- end
- return list
- end
- else
- items.map { |i| "#{i}\n" }.join
- end
- end
+ def list(items, mode = :rows, option = nil)
+ ListRenderer.new(items, mode, option, self).render
end
#
@@ -629,10 +396,10 @@ class HighLine
# and the HighLine.color() method.
#
def say( statement )
- statement = format_statement(statement)
- return unless statement.length > 0
+ statement = render_statement(statement)
+ return if statement.empty?
- out = (indentation+statement).encode(Encoding.default_external, { :undef => :replace } )
+ out = (indentation+statement)
# Don't add a newline if statement ends with whitespace, OR
# if statement ends with whitespace before a color escape code.
@@ -644,6 +411,10 @@ class HighLine
end
end
+ def render_statement(statement)
+ Statement.new(statement, self).to_s
+ end
+
#
# Set to an integer value to cause HighLine to wrap output lines at the
# indicated character limit. When +nil+, the default, no wrapping occurs. If
@@ -668,7 +439,7 @@ class HighLine
# Outputs indentation with current settings
#
def indentation
- return ' '*@indent_size*@indent_level
+ ' '*@indent_size*@indent_level
end
#
@@ -677,20 +448,17 @@ class HighLine
def indent(increase=1, statement=nil, multiline=nil)
@indent_level += increase
multi = @multi_indent
- @multi_indent = multiline unless multiline.nil?
+ @multi_indent ||= multiline
begin
- if block_given?
- yield self
- else
- say(statement)
- end
- rescue
- @multi_indent = multi
- @indent_level -= increase
- raise
+ if block_given?
+ yield self
+ else
+ say(statement)
+ end
+ ensure
+ @multi_indent = multi
+ @indent_level -= increase
end
- @multi_indent = multi
- @indent_level -= increase
end
#
@@ -706,7 +474,7 @@ class HighLine
#
def output_cols
return 80 unless @output.tty?
- terminal_size.first
+ terminal.terminal_size.first
rescue
return 80
end
@@ -717,105 +485,175 @@ class HighLine
#
def output_rows
return 24 unless @output.tty?
- terminal_size.last
+ terminal.terminal_size.last
rescue
return 24
end
+ def puts(*args)
+ @output.puts(*args)
+ end
+
+ #
+ # Creates a new HighLine instance with the same options
+ #
+ def new_scope
+ self.class.new(@input, @output, @wrap_at, @page_at, @indent_size, @indent_level)
+ end
+
private
- def format_statement statement
- statement = String(statement || "").dup
- return statement unless statement.length > 0
+ #
+ # A helper method for sending the output stream and error and repeat
+ # of the question.
+ #
+ def explain_error(error, question)
+ say(question.responses[error]) unless error.nil?
+ say(question.ask_on_error_msg)
+ end
- template = ERB.new(statement, nil, "%")
- statement = template.result(binding)
+ #
+ # Gets one answer, as opposed to HighLine#gather
+ #
+ def ask_once(question)
+
+ # readline() needs to handle its own output, but readline only supports
+ # full line reading. Therefore if question.echo is anything but true,
+ # the prompt will not be issued. And we have to account for that now.
+ # Also, JRuby-1.7's ConsoleReader.readLine() needs to be passed the prompt
+ # to handle line editing properly.
+ say(question) unless ((question.readline) and (question.echo == true and question.limit.nil?))
- statement = wrap(statement) unless @wrap_at.nil?
- statement = page_print(statement) unless @page_at.nil?
+ begin
+ question.get_response_or_default(self)
+ raise NotValidQuestionError unless question.valid_answer?
- # 'statement' is encoded in US-ASCII when using ruby 1.9.3(-p551)
- # 'indentation' is correctly encoded (same as default_external encoding)
- statement = statement.force_encoding(Encoding.default_external)
+ question.convert
- statement = statement.gsub(/\n(?!$)/,"\n#{indentation}") if @multi_indent
+ if question.confirm
+ # need to add a layer of scope (new_scope) to ask a question inside a
+ # question, without destroying instance data
- statement
- end
+ raise NoConfirmationQuestionError unless confirm(question)
+ end
- #
- # A helper method for sending the output stream and error and repeat
- # of the question.
- #
- def explain_error( error )
- say(@question.responses[error]) unless error.nil?
- if @question.responses[:ask_on_error] == :question
- say(@question)
- elsif @question.responses[:ask_on_error]
- say(@question.responses[:ask_on_error])
+ rescue NoConfirmationQuestionError
+ explain_error(nil, question)
+ retry
+
+ rescue NotInRangeQuestionError
+ explain_error(:not_in_range, question)
+ retry
+
+ rescue NotValidQuestionError
+ explain_error(:not_valid, question)
+ retry
+
+ rescue QuestionError
+ retry
+
+ rescue ArgumentError => error
+ case error.message
+ when /ambiguous/
+ # the assumption here is that OptionParser::Completion#complete
+ # (used for ambiguity resolution) throws exceptions containing
+ # the word 'ambiguous' whenever resolution fails
+ explain_error(:ambiguous_completion, question)
+ retry
+ when /invalid value for/
+ explain_error(:invalid_type, question)
+ retry
+ else
+ raise
+ end
+
+ rescue Question::NoAutoCompleteMatch
+ explain_error(:no_completion, question)
+ retry
end
+ question.answer
end
+ def confirm(question)
+ new_scope.agree(question.confirm_question(self))
+ end
+
+
+ public :ask_once
+
#
# Collects an Array/Hash full of answers as described in
# HighLine::Question.gather().
#
# Raises EOFError if input is exhausted.
#
- def gather( )
- original_question = @question
- original_question_string = @question.question
- original_gather = @question.gather
-
- verify_match = @question.verify_match
- @question.gather = false
+ def gather(question)
+ original_question_template = question.template
+ verify_match = question.verify_match
begin # when verify_match is set this loop will repeat until unique_answers == 1
- @answers = [ ]
- @gather = original_gather
- original_question.question = original_question_string
+ question.template = original_question_template
- case @gather
+ answers =
+ case question.gather
when Integer
- @answers << ask(@question)
- @gather -= 1
-
- original_question.question = ""
- until @gather.zero?
- @question = original_question
- @answers << ask(@question)
- @gather -= 1
- end
+ gather_integer(question)
when ::String, Regexp
- @answers << ask(@question)
-
- original_question.question = ""
- until (@gather.is_a?(::String) and @answers.last.to_s == @gather) or
- (@gather.is_a?(Regexp) and @answers.last.to_s =~ @gather)
- @question = original_question
- @answers << ask(@question)
- end
-
- @answers.pop
+ gather_regexp(question)
when Hash
- @answers = { }
- @gather.keys.sort.each do |key|
- @question = original_question
- @key = key
- @answers[key] = ask(@question)
- end
+ gather_hash(question)
end
- if verify_match && (unique_answers(@answers).size > 1)
- @question = original_question
- explain_error(:mismatch)
+ if verify_match && (unique_answers(answers).size > 1)
+ explain_error(:mismatch, question)
else
verify_match = false
end
end while verify_match
- original_question.verify_match ? @answer : @answers
+ question.verify_match ? last_answer(answers) : answers
+ end
+
+ public :gather
+
+ def gather_integer(question)
+ answers = []
+
+ answers << ask_once(question)
+
+ question.template = ""
+
+ (question.gather-1).times do
+ answers << ask_once(question)
+ end
+
+ answers
+ end
+
+ def gather_regexp(question)
+ answers = []
+
+ answers << ask_once(question)
+
+ question.template = ""
+ until (question.gather.is_a?(::String) and answers.last.to_s == question.gather) or
+ (question.gather.is_a?(Regexp) and answers.last.to_s =~ question.gather)
+ answers << ask_once(question)
+ end
+
+ answers.pop
+ answers
+ end
+
+ def gather_hash(question)
+ answers = {}
+
+ question.gather.keys.sort.each do |key|
+ @key = key
+ answers[key] = ask_once(question)
+ end
+ answers
end
#
@@ -823,10 +661,14 @@ class HighLine
# for finding whether a list of answers match or differ
# from each other.
#
- def unique_answers(list = @answers)
+ def unique_answers(list)
(list.respond_to?(:values) ? list.values : list).uniq
end
+ def last_answer(answers)
+ answers.respond_to?(:values) ? answers.values.last : answers.last
+ end
+
#
# Read a line of input from the input stream and process whitespace as
# requested by the Question object.
@@ -836,223 +678,91 @@ class HighLine
#
# Raises EOFError if input is exhausted.
#
- def get_line( )
- if @question.readline
- require "readline" # load only if needed
-
- # capture say()'s work in a String to feed to readline()
- old_output = @output
- @output = StringIO.new
- say(@question)
- question = @output.string
- @output = old_output
-
- # prep auto-completion
- Readline.completion_proc = lambda do |string|
- @question.selection.grep(/\A#{Regexp.escape(string)}/)
- end
+ def get_line(question)
+ terminal.get_line(question, self)
+ end
+
+ def get_response_line_mode(question)
+ if question.echo == true and question.limit.nil?
+ get_line(question)
+ else
+ line = ""
+
+ terminal.raw_no_echo_mode_exec do
+ while character = terminal.get_character(@input)
+ break if character == "\n" or character == "\r"
+
+ # honor backspace and delete
+ if character == "\b"
+ chopped = line.chop!
+ output_erase_char if chopped and question.echo
+ else
+ line << character
+ @output.print(line[-1]) if question.echo == true
+ @output.print(question.echo) if question.echo and question.echo != true
+ end
+
+ @output.flush
- # work-around ugly readline() warnings
- old_verbose = $VERBOSE
- $VERBOSE = nil
- raw_answer = Readline.readline(question, true)
- if raw_answer.nil?
- if @@track_eof
- raise EOFError, "The input stream is exhausted."
- else
- raw_answer = String.new # Never return nil
+ break if question.limit and line.size == question.limit
end
end
- answer = @question.change_case(
- @question.remove_whitespace(raw_answer))
- $VERBOSE = old_verbose
- answer
- else
- if JRUBY
- statement = format_statement(@question)
- raw_answer = @java_console.readLine(statement, nil)
-
- raise EOFError, "The input stream is exhausted." if raw_answer.nil? and
- @@track_eof
+ if question.overwrite
+ @output.print("\r#{HighLine.Style(:erase_line).code}")
+ @output.flush
else
- raise EOFError, "The input stream is exhausted." if @@track_eof and
- @input.eof?
- raw_answer = @input.gets
+ say("\n")
end
- @question.change_case(@question.remove_whitespace(raw_answer))
+ question.format_answer(line)
end
end
- #
- # Return a line or character of input, as requested for this question.
- # Character input will be returned as a single character String,
- # not an Integer.
- #
- # This question's _first_answer_ will be returned instead of input, if set.
- #
- # Raises EOFError if input is exhausted.
- #
- def get_response( )
- return @question.first_answer if @question.first_answer?
-
- if @question.character.nil?
- if @question.echo == true and @question.limit.nil?
- get_line
- else
- raw_no_echo_mode
-
- line = "".encode(Encoding::BINARY)
- backspace_limit = 0
- begin
-
- while character = get_character(@input)
- # honor backspace and delete
- if character == 127 or character == 8
- line = line.force_encoding(Encoding.default_external)
- line.slice!(-1, 1)
- backspace_limit -= 1
- line = line.force_encoding(Encoding::BINARY)
- else
- line << character.chr
- backspace_limit = line.dup.force_encoding(Encoding.default_external).size
- end
- # looking for carriage return (decimal 13) or
- # newline (decimal 10) in raw input
- break if character == 13 or character == 10
- if @question.echo != false
- if character == 127 or character == 8
- # only backspace if we have characters on the line to
- # eliminate, otherwise we'll tromp over the prompt
- if backspace_limit >= 0 then
- @output.print("\b#{HighLine.Style(:erase_char).code}")
- else
- # do nothing
- end
- else
- line_with_next_char_encoded = line.dup.force_encoding(Encoding.default_external)
- # For multi-byte character, does this
- # last character completes the character?
- # Then print it.
- if line_with_next_char_encoded.valid_encoding?
- if @question.echo == true
- @output.print(line_with_next_char_encoded[-1])
- else
- @output.print(@question.echo)
- end
- end
- end
- @output.flush
- end
- break if @question.limit and line.size == @question.limit
- end
- ensure
- restore_mode
- end
- if @question.overwrite
- @output.print("\r#{HighLine.Style(:erase_line).code}")
- @output.flush
- else
- say("\n")
- end
-
- @question.change_case(@question.remove_whitespace(line.force_encoding(Encoding.default_external)))
- end
- else
- if JRUBY #prompt has not been shown
- say @question
- end
+ def output_erase_char
+ @output.print("\b#{HighLine.Style(:erase_char).code}")
+ end
- raw_no_echo_mode
- begin
- if @question.character == :getc
- response = @input.getbyte.chr
- else
- response = get_character(@input).chr
- if @question.overwrite
- @output.print("\r#{HighLine.Style(:erase_line).code}")
- @output.flush
- else
- echo = if @question.echo == true
- response
- elsif @question.echo != false
- @question.echo
- else
- ""
- end
- say("#{echo}\n")
- end
- end
- ensure
- restore_mode
- end
- @question.change_case(response)
+ def get_response_getc_mode(question)
+ terminal.raw_no_echo_mode_exec do
+ response = @input.getc
+ question.format_answer(response)
end
end
- #
- # Page print a series of at most _page_at_ lines for _output_. After each
- # page is printed, HighLine will pause until the user presses enter/return
- # then display the next page of data.
- #
- # Note that the final page of _output_ is *not* printed, but returned
- # instead. This is to support any special handling for the final sequence.
- #
- def page_print( output )
- lines = output.scan(/[^\n]*\n?/)
- while lines.size > @page_at
- @output.puts lines.slice!(0...@page_at).join
- @output.puts
- # Return last line if user wants to abort paging
- return (["...\n"] + lines.slice(-2,1)).join unless continue_paging?
+ def get_response_character_mode(question)
+ terminal.raw_no_echo_mode_exec do
+ response = terminal.get_character(@input)
+ if question.overwrite
+ erase_current_line
+ else
+ echo = get_echo(question, response)
+ say("#{echo}\n")
+ end
+ question.format_answer(response)
end
- return lines.join
end
- #
- # Ask user if they wish to continue paging output. Allows them to type "q" to
- # cancel the paging process.
- #
- def continue_paging?
- command = HighLine.new(@input, @output).ask(
- "-- press enter/return to continue or q to stop -- "
- ) { |q| q.character = true }
- command !~ /\A[qQ]\Z/ # Only continue paging if Q was not hit.
+ def erase_current_line
+ @output.print("\r#{HighLine.Style(:erase_line).code}")
+ @output.flush
end
- #
- # Wrap a sequence of _lines_ at _wrap_at_ characters per line. Existing
- # newlines will not be affected by this process, but additional newlines
- # may be added.
- #
- def wrap( text )
- wrapped = [ ]
- text.each_line do |line|
- # take into account color escape sequences when wrapping
- wrap_at = @wrap_at + (line.length - actual_length(line))
- while line =~ /([^\n]{#{wrap_at + 1},})/
- search = $1.dup
- replace = $1.dup
- if index = replace.rindex(" ", wrap_at)
- replace[index, 1] = "\n"
- replace.sub!(/\n[ \t]+/, "\n")
- line.sub!(search, replace)
- else
- line[$~.begin(1) + wrap_at, 0] = "\n"
- end
- end
- wrapped << line
+ def get_echo(question, response)
+ if question.echo == true
+ response
+ elsif question.echo != false
+ question.echo
+ else
+ ""
end
- return wrapped.join
end
- #
- # Returns the length of the passed +string_with_escapes+, minus and color
- # sequence escapes.
- #
- def actual_length( string_with_escapes )
- string_with_escapes.to_s.gsub(/\e\[\d{1,2}m/, "").length
+ public :get_response_character_mode, :get_response_line_mode
+ public :get_response_getc_mode
+
+ def actual_length(text)
+ Wrapper.actual_length text
end
end
diff --git a/lib/highline/color_scheme.rb b/lib/highline/color_scheme.rb
index 601781f..0d07c42 100644
--- a/lib/highline/color_scheme.rb
+++ b/lib/highline/color_scheme.rb
@@ -1,3 +1,5 @@
+# coding: utf-8
+
# color_scheme.rb
#
# Created by Jeremy Hinegardner on 2007-01-24
diff --git a/lib/highline/compatibility.rb b/lib/highline/compatibility.rb
index 7a99819..1083686 100644
--- a/lib/highline/compatibility.rb
+++ b/lib/highline/compatibility.rb
@@ -1,3 +1,5 @@
+# coding: utf-8
+
unless STDIN.respond_to? :getbyte
class IO
alias_method :getbyte, :getc
diff --git a/lib/highline/import.rb b/lib/highline/import.rb
index 95f0301..7a0e6fa 100644
--- a/lib/highline/import.rb
+++ b/lib/highline/import.rb
@@ -1,3 +1,5 @@
+# coding: utf-8
+
# import.rb
#
# Created by James Edward Gray II on 2005-04-26.
diff --git a/lib/highline/list.rb b/lib/highline/list.rb
new file mode 100644
index 0000000..d5c127b
--- /dev/null
+++ b/lib/highline/list.rb
@@ -0,0 +1,93 @@
+# coding: utf-8
+
+class HighLine
+ class List
+ attr_reader :items, :cols
+ attr_reader :transpose_mode, :col_down_mode
+
+ def initialize(items, options = {})
+ @items = items
+ @transpose_mode = options.fetch(:transpose) { false }
+ @col_down_mode = options.fetch(:col_down) { false }
+ @cols = options.fetch(:cols) { 1 }
+ build
+ end
+
+ def transpose
+ first_row = @list[0]
+ other_rows = @list[1..-1]
+ @list = first_row.zip(*other_rows)
+ self
+ end
+
+ def col_down
+ slice_by_rows
+ transpose
+ self
+ end
+
+ def slice_by_rows
+ @list = items_sliced_by_rows
+ self
+ end
+
+ def slice_by_cols
+ @list = items_sliced_by_cols
+ self
+ end
+
+ def cols=(cols)
+ @cols = cols
+ build
+ end
+
+ def to_a
+ list
+ end
+
+ def list
+ @list.dup
+ end
+
+ def to_s
+ list.map { |row| stringfy(row) }.join
+ end
+
+ def row_join_string
+ @row_join_string ||= " "
+ end
+
+ def row_join_string=(string)
+ @row_join_string = string
+ end
+
+ def row_join_str_size
+ row_join_string.size
+ end
+
+ private
+
+ def build
+ slice_by_cols
+ transpose if transpose_mode
+ col_down if col_down_mode
+ self
+ end
+
+ def items_sliced_by_cols
+ items.each_slice(cols).to_a
+ end
+
+ def items_sliced_by_rows
+ items.each_slice(row_count).to_a
+ end
+
+ def row_count
+ (items.count / cols.to_f).ceil
+ end
+
+ def stringfy(row)
+ row.compact.join(row_join_string) + "\n"
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/highline/list_renderer.rb b/lib/highline/list_renderer.rb
new file mode 100644
index 0000000..95fc490
--- /dev/null
+++ b/lib/highline/list_renderer.rb
@@ -0,0 +1,232 @@
+# coding: utf-8
+
+require 'highline/template_renderer'
+require 'highline/wrapper'
+require 'highline/list'
+
+class HighLine::ListRenderer
+ attr_reader :items, :mode, :option, :highline
+
+ def initialize(items, mode = :rows, option = nil, highline)
+ @highline = highline
+ @mode = mode
+ @option = option
+ @items = render_list_items(items)
+ end
+
+ #
+ # This method is a utility for quickly and easily laying out lists. It can
+ # be accessed within ERb replacements of any text that will be sent to the
+ # user.
+ #
+ # The only required parameter is _items_, which should be the Array of items
+ # to list. A specified _mode_ controls how that list is formed and _option_
+ # has different effects, depending on the _mode_. Recognized modes are:
+ #
+ # <tt>:columns_across</tt>:: _items_ will be placed in columns,
+ # flowing from left to right. If given,
+ # _option_ is the number of columns to be
+ # used. When absent, columns will be
+ # determined based on _wrap_at_ or a
+ # default of 80 characters.
+ # <tt>:columns_down</tt>:: Identical to <tt>:columns_across</tt>,
+ # save flow goes down.
+ # <tt>:uneven_columns_across</tt>:: Like <tt>:columns_across</tt> but each
+ # column is sized independently.
+ # <tt>:uneven_columns_down</tt>:: Like <tt>:columns_down</tt> but each
+ # column is sized independently.
+ # <tt>:inline</tt>:: All _items_ are placed on a single line.
+ # The last two _items_ are separated by
+ # _option_ or a default of " or ". All
+ # other _items_ are separated by ", ".
+ # <tt>:rows</tt>:: The default mode. Each of the _items_ is
+ # placed on its own line. The _option_
+ # parameter is ignored in this mode.
+ #
+ # Each member of the _items_ Array is passed through ERb and thus can contain
+ # their own expansions. Color escape expansions do not contribute to the
+ # final field width.
+ #
+ def render
+ return "" if items.empty?
+
+ case mode
+ when :inline
+ list_inline_mode
+ when :columns_across
+ list_columns_across_mode
+ when :columns_down
+ list_columns_down_mode
+ when :uneven_columns_across
+ list_uneven_columns_mode
+ when :uneven_columns_down
+ list_uneven_columns_down_mode
+ else
+ list_default_mode
+ end
+ end
+
+ private
+
+ def render_list_items(items)
+ items.to_ary.map do |item|
+ item = String(item)
+ template = ERB.new(item, nil, "%")
+ template_renderer = HighLine::TemplateRenderer.new(template, self, highline)
+ template_renderer.render
+ end
+ end
+
+ def list_default_mode
+ items.map { |i| "#{i}\n" }.join
+ end
+
+ def list_inline_mode
+ end_separator = option || " or "
+
+ if items.size == 1
+ items.first
+ else
+ items[0..-2].join(", ") + "#{end_separator}#{items.last}"
+ end
+ end
+
+ def list_columns_across_mode
+ HighLine::List.new(right_padded_items, cols: col_count).to_s
+ end
+
+ def list_columns_down_mode
+ HighLine::List.new(right_padded_items, cols: col_count, col_down: true).to_s
+ end
+
+ def list_uneven_columns_mode(list=nil)
+ list ||= HighLine::List.new(items)
+
+ col_max = option || items.size
+ col_max.downto(1) do |column_count|
+ list.cols = column_count
+ widths = get_col_widths(list)
+
+ if column_count == 1 or # last guess
+ inside_line_size_limit?(widths) or # good guess
+ option # defined by user
+ return pad_uneven_rows(list, widths)
+ end
+ end
+ end
+
+ def list_uneven_columns_down_mode
+ list = HighLine::List.new(items, col_down: true)
+ list_uneven_columns_mode(list)
+ end
+
+ def pad_uneven_rows(list, widths)
+ right_padded_list = Array(list).map do |row|
+ right_pad_row(row.compact, widths)
+ end
+ stringfy_list(right_padded_list)
+ end
+
+ def stringfy_list(list)
+ list.map { |row| row_to_s(row) }.join
+ end
+
+ def row_to_s(row)
+ row.compact.join(row_join_string) + "\n"
+ end
+
+ def right_pad_row(row, widths)
+ row.zip(widths).map do |field, width|
+ right_pad_field(field, width)
+ end
+ end
+
+ def right_pad_field(field, width)
+ field = String(field) # nil protection
+ pad_size = width - actual_length(field)
+ field + (pad_char * pad_size)
+ end
+
+ def get_col_widths(lines)
+ lines = transpose(lines)
+ get_segment_widths(lines)
+ end
+
+ def get_row_widths(lines)
+ get_segment_widths(lines)
+ end
+
+ def get_segment_widths(lines)
+ lines.map do |col|
+ actual_lengths_for(col).max
+ end
+ end
+
+ def actual_lengths_for(line)
+ line.map do |item|
+ actual_length(item)
+ end
+ end
+
+ def transpose(lines)
+ lines = Array(lines)
+ first_line = lines.shift
+ first_line.zip(*lines)
+ end
+
+ def inside_line_size_limit?(widths)
+ line_size = widths.inject(0) { |sum, n| sum + n + row_join_str_size }
+ line_size <= line_size_limit + row_join_str_size
+ end
+
+ def actual_length(text)
+ HighLine::Wrapper.actual_length text
+ end
+
+ def items_max_length
+ @items_max_length ||= max_length(items)
+ end
+
+ def max_length(items)
+ items.map { |item| actual_length(item) }.max
+ end
+
+ def line_size_limit
+ @line_size_limit ||= ( highline.wrap_at || 80 )
+ end
+
+ def row_join_string
+ @row_join_string ||= " "
+ end
+
+ def row_join_string=(string)
+ @row_join_string = string
+ end
+
+ def row_join_str_size
+ row_join_string.size
+ end
+
+ def get_col_count
+ (line_size_limit + row_join_str_size) /
+ (items_max_length + row_join_str_size)
+ end
+
+ def col_count
+ option || get_col_count
+ end
+
+ def right_padded_items
+ items.map do |item|
+ right_pad_field(item, items_max_length)
+ end
+ end
+
+ def pad_char
+ " "
+ end
+
+ def row_count
+ (items.count / col_count.to_f).ceil
+ end
+end \ No newline at end of file
diff --git a/lib/highline/menu.rb b/lib/highline/menu.rb
index 44c41b7..71c4f47 100644
--- a/lib/highline/menu.rb
+++ b/lib/highline/menu.rb
@@ -1,3 +1,5 @@
+# coding: utf-8
+
# menu.rb
#
# Created by Gregory Thomas Brown on 2005-05-10.
@@ -235,11 +237,10 @@ class HighLine
# <tt>:menu_only</tt>:: Just the menu items, followed up by a likely
# short _prompt_.
# <i>any ERb String</i>:: Will be taken as the literal _layout_. This
- # String can access <tt>@header</tt>,
- # <tt>@menu</tt> and <tt>@prompt</tt>, but is
- # otherwise evaluated in the typical HighLine
- # context, to provide access to utilities like
- # HighLine.list() primarily.
+ # String can access <tt>header</tt>,
+ # <tt>menu</tt> and <tt>prompt</tt>, but is
+ # otherwise evaluated in the TemplateRenderer
+ # context so each method is properly delegated.
#
# If set to either <tt>:one_line</tt>, or <tt>:menu_only</tt>, _index_
# will default to <tt>:none</tt> and _flow_ will default to
@@ -348,19 +349,19 @@ class HighLine
def to_s( )
case @layout
when :list
- '<%= if @header.nil? then '' else "#{@header}:\n" end %>' +
- "<%= list( @menu, #{@flow.inspect},
+ '<%= if header.nil? then '' else "#{header}:\n" end %>' +
+ "<%= list( menu, #{@flow.inspect},
#{@list_option.inspect} ) %>" +
- "<%= @prompt %>"
+ "<%= prompt %>"
when :one_line
- '<%= if @header.nil? then '' else "#{@header}: " end %>' +
- "<%= @prompt %>" +
- "(<%= list( @menu, #{@flow.inspect},
+ '<%= if header.nil? then '' else "#{header}: " end %>' +
+ "<%= prompt %>" +
+ "(<%= list( menu, #{@flow.inspect},
#{@list_option.inspect} ) %>)" +
- "<%= @prompt[/\s*$/] %>"
+ "<%= prompt[/\s*$/] %>"
when :menu_only
- "<%= list( @menu, #{@flow.inspect},
- #{@list_option.inspect} ) %><%= @prompt %>"
+ "<%= list( menu, #{@flow.inspect},
+ #{@list_option.inspect} ) %><%= prompt %>"
else
@layout
end
diff --git a/lib/highline/paginator.rb b/lib/highline/paginator.rb
new file mode 100644
index 0000000..f02ea8c
--- /dev/null
+++ b/lib/highline/paginator.rb
@@ -0,0 +1,43 @@
+# coding: utf-8
+
+class HighLine
+ class Paginator
+ attr_reader :highline
+
+ def initialize(highline)
+ @highline = highline
+ end
+
+ #
+ # Page print a series of at most _page_at_ lines for _output_. After each
+ # page is printed, HighLine will pause until the user presses enter/return
+ # then display the next page of data.
+ #
+ # Note that the final page of _output_ is *not* printed, but returned
+ # instead. This is to support any special handling for the final sequence.
+ #
+ def page_print(text)
+ return text unless highline.page_at
+
+ lines = text.scan(/[^\n]*\n?/)
+ while lines.size > highline.page_at
+ highline.puts lines.slice!(0...highline.page_at).join
+ highline.puts
+ # Return last line if user wants to abort paging
+ return (["...\n"] + lines.slice(-2,1)).join unless continue_paging?
+ end
+ return lines.join
+ end
+
+ #
+ # Ask user if they wish to continue paging output. Allows them to type "q" to
+ # cancel the paging process.
+ #
+ def continue_paging?
+ command = highline.new_scope.ask(
+ "-- press enter/return to continue or q to stop -- "
+ ) { |q| q.character = true }
+ command !~ /\A[qQ]\Z/ # Only continue paging if Q was not hit.
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/highline/question.rb b/lib/highline/question.rb
index fa8445a..0ad9607 100755
--- a/lib/highline/question.rb
+++ b/lib/highline/question.rb
@@ -1,3 +1,5 @@
+# coding: utf-8
+
# question.rb
#
# Created by James Edward Gray II on 2005-04-26.
@@ -8,6 +10,7 @@
require "optparse"
require "date"
require "pathname"
+require "highline/question/answer_converter"
class HighLine
#
@@ -23,15 +26,15 @@ class HighLine
end
#
- # Create an instance of HighLine::Question. Expects a _question_ to ask
+ # Create an instance of HighLine::Question. Expects a _template_ to ask
# (can be <tt>""</tt>) and an _answer_type_ to convert the answer to.
# The _answer_type_ parameter must be a type recognized by
# Question.convert(). If given, a block is yielded the new Question
# object to allow custom initialization.
#
- def initialize( question, answer_type )
+ def initialize( template, answer_type )
# initialize instance data
- @question = question.dup
+ @template = template.dup
@answer_type = answer_type
@completion = @answer_type
@@ -62,8 +65,14 @@ class HighLine
build_responses
end
+ attr_reader :directory
+
# The ERb template of the question to be asked.
- attr_accessor :question
+ attr_accessor :template
+
+ # The answer, set by HighLine#ask
+ attr_accessor :answer
+
# The type that will be used to convert this answer.
attr_accessor :answer_type
# For Auto-completion
@@ -215,11 +224,8 @@ class HighLine
# Question if a default was set and the answer is empty.
#
def answer_or_default( answer_string )
- if answer_string.length == 0 and not @default.nil?
- @default
- else
- answer_string
- end
+ return @default if answer_string.empty? && @default
+ answer_string
end
#
@@ -230,35 +236,28 @@ class HighLine
def build_responses(message_source = answer_type, new_hash_wins = false)
append_default if [::String, Symbol].include? default.class
- choice_error_str_func = lambda do
- message_source.is_a?(Array) \
- ? '[' + message_source.map { |s| "#{s}" }.join(', ') + ']' \
- : message_source.inspect
- end
-
old_hash = @responses
- new_hash = { :ambiguous_completion =>
- "Ambiguous choice. Please choose one of " +
- choice_error_str_func.call + '.',
- :ask_on_error =>
- "? ",
- :invalid_type =>
- "You must enter a valid #{message_source}.",
- :no_completion =>
- "You must choose one of " + choice_error_str_func.call + '.',
- :not_in_range =>
- "Your answer isn't within the expected range " +
- "(#{expected_range}).",
- :mismatch =>
- "Your entries didn't match.",
- :not_valid =>
- "Your answer isn't valid (must match " +
- "#{@validate.inspect})." }
+ new_hash = build_responses_new_hash(message_source)
@responses = new_hash_wins ? old_hash.merge(new_hash) : new_hash.merge(old_hash)
end
+ def build_responses_new_hash(message_source)
+ { :ambiguous_completion => "Ambiguous choice. Please choose one of " +
+ choice_error_str(message_source) + '.',
+ :ask_on_error => "? ",
+ :invalid_type => "You must enter a valid #{message_source}.",
+ :no_completion => "You must choose one of " +
+ choice_error_str(message_source) + '.',
+ :not_in_range => "Your answer isn't within the expected range " +
+ "(#{expected_range}).",
+ :mismatch => "Your entries didn't match.",
+ :not_valid => "Your answer isn't valid (must match " +
+ "#{@validate.inspect})." }
+ end
+
+
#
# Returns the provided _answer_string_ after changing character case by
# the rules of this Question. Valid settings for whitespace are:
@@ -314,37 +313,21 @@ class HighLine
# This method throws ArgumentError, if the conversion cannot be
# completed for any reason.
#
- def convert( answer_string )
- if @answer_type.nil?
- answer_string
- elsif [::String, HighLine::String].include?(@answer_type)
- HighLine::String(answer_string)
- elsif [Float, Integer, String].include?(@answer_type)
- Kernel.send(@answer_type.to_s.to_sym, answer_string)
- elsif @answer_type == Symbol
- answer_string.to_sym
- elsif @answer_type == Regexp
- Regexp.new(answer_string)
- elsif @answer_type.is_a?(Array) or [File, Pathname].include?(@answer_type)
- # cheating, using OptionParser's Completion module
- choices = selection
- choices.extend(OptionParser::Completion)
- answer = choices.complete(answer_string)
- if answer.nil?
- raise NoAutoCompleteMatch
- end
- if @answer_type.is_a?(Array)
- answer.last
- elsif @answer_type == File
- File.open(File.join(@directory.to_s, answer.last))
- else
- Pathname.new(File.join(@directory.to_s, answer.last))
- end
- elsif [Date, DateTime].include?(@answer_type) or @answer_type.is_a?(Class)
- @answer_type.parse(answer_string)
- elsif @answer_type.is_a?(Proc)
- @answer_type[answer_string]
- end
+ def convert
+ Question::AnswerConverter.new(self).convert
+ end
+
+ def check_range
+ raise NotInRangeQuestionError unless in_range?
+ end
+
+ def choices_complete(answer_string)
+ # cheating, using OptionParser's Completion module
+ choices = selection
+ choices.extend(OptionParser::Completion)
+ answer = choices.complete(answer_string)
+ raise NoAutoCompleteMatch unless answer
+ answer
end
# Returns an English explanation of the current range settings.
@@ -381,10 +364,10 @@ class HighLine
# _in_ attribute. Otherwise, +false+ is returned. Any +nil+ attributes
# are not checked.
#
- def in_range?( answer_object )
- (@above.nil? or answer_object > @above) and
- (@below.nil? or answer_object < @below) and
- (@in.nil? or @in.include?(answer_object))
+ def in_range?
+ (@above.nil? or answer > @above) and
+ (@below.nil? or answer < @below) and
+ (@in.nil? or @in.include?(answer))
end
#
@@ -423,6 +406,12 @@ class HighLine
end
end
+ def format_answer(answer_string)
+ answer_string = String(answer_string)
+ answer_string = remove_whitespace(answer_string)
+ change_case(answer_string)
+ end
+
#
# Returns an Array of valid answers to this question. These answers are
# only known when _answer_type_ is set to an Array of choices, File, or
@@ -440,9 +429,9 @@ class HighLine
end
end
- # Stringifies the question to be asked.
+ # Stringifies the template to be asked.
def to_s
- @question
+ @template
end
#
@@ -452,10 +441,61 @@ class HighLine
# It's important to realize that an answer is validated after whitespace
# and case handling.
#
- def valid_answer?( answer_string )
+ def valid_answer?
@validate.nil? or
- (@validate.is_a?(Regexp) and answer_string =~ @validate) or
- (@validate.is_a?(Proc) and @validate[answer_string])
+ (@validate.is_a?(Regexp) and answer =~ @validate) or
+ (@validate.is_a?(Proc) and @validate[answer])
+ end
+
+ #
+ # Return a line or character of input, as requested for this question.
+ # Character input will be returned as a single character String,
+ # not an Integer.
+ #
+ # This question's _first_answer_ will be returned instead of input, if set.
+ #
+ # Raises EOFError if input is exhausted.
+ #
+ def get_response(highline)
+ return first_answer if first_answer?
+
+ case character
+ when :getc
+ highline.get_response_getc_mode(self)
+ when true
+ highline.get_response_character_mode(self)
+ else
+ highline.get_response_line_mode(self)
+ end
+ end
+
+ def get_response_or_default(highline)
+ self.answer = answer_or_default(get_response(highline))
+ end
+
+ def ask_at(highline)
+ return highline.gather(self) if gather
+ return highline.ask_once(self)
+ end
+
+ def confirm_question(highline)
+ if confirm == true
+ "Are you sure? "
+ else
+ # evaluate ERb under initial scope, so it will have
+ # access to question and answer
+ template = ERB.new(confirm, nil, "%")
+ template_renderer = TemplateRenderer.new(template, self, highline)
+ template_renderer.render
+ end
+ end
+
+ def ask_on_error_msg
+ if responses[:ask_on_error] == :question
+ self
+ elsif responses[:ask_on_error]
+ responses[:ask_on_error]
+ end
end
private
@@ -466,14 +506,22 @@ class HighLine
# not affected.
#
def append_default( )
- if @question =~ /([\t ]+)\Z/
- @question << "|#{@default}|#{$1}"
- elsif @question == ""
- @question << "|#{@default}| "
- elsif @question[-1, 1] == "\n"
- @question[-2, 0] = " |#{@default}|"
+ if @template =~ /([\t ]+)\Z/
+ @template << "|#{@default}|#{$1}"
+ elsif @template == ""
+ @template << "|#{@default}| "
+ elsif @template[-1, 1] == "\n"
+ @template[-2, 0] = " |#{@default}|"
+ else
+ @template << " |#{@default}|"
+ end
+ end
+
+ def choice_error_str(message_source)
+ if message_source.is_a? Array
+ '[' + message_source.join(', ') + ']'
else
- @question << " |#{@default}|"
+ message_source.inspect
end
end
end
diff --git a/lib/highline/question/answer_converter.rb b/lib/highline/question/answer_converter.rb
new file mode 100644
index 0000000..b840440
--- /dev/null
+++ b/lib/highline/question/answer_converter.rb
@@ -0,0 +1,84 @@
+# coding: utf-8
+
+require 'forwardable'
+
+class HighLine
+ class Question
+ class AnswerConverter
+ extend Forwardable
+
+ def_delegators :@question,
+ :answer, :answer=, :check_range,
+ :directory, :answer_type, :choices_complete
+
+ def initialize(question)
+ @question = question
+ end
+
+ def convert
+ return unless answer_type
+
+ self.answer = convert_by_answer_type
+ check_range
+ answer
+ end
+
+ private
+
+ def convert_by_answer_type
+ if answer_type.respond_to? :parse
+ answer_type.parse(answer)
+ elsif answer_type.is_a? Class
+ send(answer_type.name)
+ else
+ send(answer_type.class.name)
+ end
+ end
+
+ def String
+ HighLine::String(answer)
+ end
+
+ # That's a weird name for a method!
+ # But it's working ;-)
+ define_method "HighLine::String" do
+ HighLine::String(answer)
+ end
+
+ def Integer
+ Kernel.send(:Integer, answer)
+ end
+
+ def Float
+ Kernel.send(:Float, answer)
+ end
+
+ def Symbol
+ answer.to_sym
+ end
+
+ def Regexp
+ Regexp.new(answer)
+ end
+
+ def File
+ self.answer = choices_complete(answer)
+ File.open(File.join(directory.to_s, answer.last))
+ end
+
+ def Pathname
+ self.answer = choices_complete(answer)
+ Pathname.new(File.join(directory.to_s, answer.last))
+ end
+
+ def Array
+ self.answer = choices_complete(answer)
+ answer.last
+ end
+
+ def Proc
+ answer_type.call(answer)
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/highline/simulate.rb b/lib/highline/simulate.rb
index 3596f59..4bad8f9 100644
--- a/lib/highline/simulate.rb
+++ b/lib/highline/simulate.rb
@@ -1,3 +1,5 @@
+# coding: utf-8
+
# simulate.rb
#
# Created by Andy Rossmeissl on 2012-04-29.
diff --git a/lib/highline/statement.rb b/lib/highline/statement.rb
new file mode 100644
index 0000000..870495c
--- /dev/null
+++ b/lib/highline/statement.rb
@@ -0,0 +1,58 @@
+# coding: utf-8
+
+require 'highline/wrapper'
+require 'highline/paginator'
+require 'highline/template_renderer'
+
+class HighLine::Statement
+ attr_reader :source, :highline
+ attr_reader :template_string
+
+ def initialize(source, highline)
+ @highline = highline
+ @source = source
+ @template_string = stringfy(source)
+ end
+
+ def statement
+ @statement ||= format_statement
+ end
+
+ def to_s
+ statement
+ end
+
+ private
+
+ def stringfy(template_string)
+ String(template_string || "").dup
+ end
+
+ def format_statement
+ return template_string unless template_string.length > 0
+
+ statement = render_template
+
+ statement = HighLine::Wrapper.wrap(statement, highline.wrap_at)
+ statement = HighLine::Paginator.new(highline).page_print(statement)
+
+ statement = statement.gsub(/\n(?!$)/,"\n#{highline.indentation}") if highline.multi_indent
+ statement
+ end
+
+ def render_template
+ # Assigning to a local var so it may be
+ # used inside instance eval block
+
+ template_renderer = TemplateRenderer.new(template, source, highline)
+ template_renderer.render
+ end
+
+ def template
+ @template ||= ERB.new(template_string, nil, "%")
+ end
+
+ def self.const_missing(constant)
+ HighLine.const_get(constant)
+ end
+end \ No newline at end of file
diff --git a/lib/highline/string_extensions.rb b/lib/highline/string_extensions.rb
index c72aff0..a094bb4 100644
--- a/lib/highline/string_extensions.rb
+++ b/lib/highline/string_extensions.rb
@@ -1,3 +1,5 @@
+# coding: utf-8
+
# Extensions for class String
#
# HighLine::String is a subclass of String with convenience methods added for colorization.
diff --git a/lib/highline/style.rb b/lib/highline/style.rb
index 0d1a1f2..72871b6 100755
--- a/lib/highline/style.rb
+++ b/lib/highline/style.rb
@@ -1,3 +1,5 @@
+# coding: utf-8
+
# color_scheme.rb
#
# Created by Richard LeBer on 2011-06-27.
diff --git a/lib/highline/system_extensions.rb b/lib/highline/system_extensions.rb
deleted file mode 100755
index c36af70..0000000
--- a/lib/highline/system_extensions.rb
+++ /dev/null
@@ -1,251 +0,0 @@
-# system_extensions.rb
-#
-# Created by James Edward Gray II on 2006-06-14.
-# Copyright 2006 Gray Productions. All rights reserved.
-#
-# This is Free Software. See LICENSE and COPYING for details.
-
-require "highline/compatibility"
-
-class HighLine
- module SystemExtensions
- JRUBY = defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
-
- if JRUBY
- def initialize_system_extensions
- require 'java'
- require 'readline'
- if JRUBY_VERSION =~ /^1.7/
- java_import 'jline.console.ConsoleReader'
-
- input = @input && @input.to_inputstream
- output = @output && @output.to_outputstream
-
- @java_console = ConsoleReader.new(input, output)
- @java_console.set_history_enabled(false)
- @java_console.set_bell_enabled(true)
- @java_console.set_pagination_enabled(false)
- @java_terminal = @java_console.getTerminal
- elsif JRUBY_VERSION =~ /^1.6/
- java_import 'java.io.OutputStreamWriter'
- java_import 'java.nio.channels.Channels'
- java_import 'jline.ConsoleReader'
- java_import 'jline.Terminal'
-
- @java_input = Channels.newInputStream(@input.to_channel)
- @java_output = OutputStreamWriter.new(Channels.newOutputStream(@output.to_channel))
- @java_terminal = Terminal.getTerminal
- @java_console = ConsoleReader.new(@java_input, @java_output)
- @java_console.setUseHistory(false)
- @java_console.setBellEnabled(true)
- @java_console.setUsePagination(false)
- end
- end
- end
-
- extend self
-
- #
- # This section builds character reading and terminal size functions
- # to suit the proper platform we're running on. Be warned: Here be
- # dragons!
- #
- if RUBY_PLATFORM =~ /mswin(?!ce)|mingw|bccwin/i
- begin
- require "fiddle"
-
- module WinAPI
- include Fiddle
- Handle = RUBY_VERSION >= "2.0.0" ? Fiddle::Handle : DL::Handle
- Kernel32 = Handle.new("kernel32")
- Crt = Handle.new("msvcrt") rescue Handle.new("crtdll")
-
- def self._getch
- @@_m_getch ||= Function.new(Crt["_getch"], [], TYPE_INT)
- @@_m_getch.call
- end
-
- def self.GetStdHandle(handle_type)
- @@get_std_handle ||= Function.new(Kernel32["GetStdHandle"], [-TYPE_INT], -TYPE_INT)
- @@get_std_handle.call(handle_type)
- end
-
- def self.GetConsoleScreenBufferInfo(cons_handle, lp_buffer)
- @@get_console_screen_buffer_info ||=
- Function.new(Kernel32["GetConsoleScreenBufferInfo"], [TYPE_LONG, TYPE_VOIDP], TYPE_INT)
- @@get_console_screen_buffer_info.call(cons_handle, lp_buffer)
- end
- end
- rescue LoadError
- require "dl/import"
-
- module WinAPI
- if defined?(DL::Importer)
- # Ruby 1.9
- extend DL::Importer
- else
- # Ruby 1.8
- extend DL::Importable
- end
- begin
- dlload "msvcrt", "kernel32"
- rescue DL::DLError
- dlload "crtdll", "kernel32"
- end
- extern "unsigned long _getch()"
- extern "unsigned long GetConsoleScreenBufferInfo(unsigned long, void*)"
- extern "unsigned long GetStdHandle(unsigned long)"
-
- # Ruby 1.8 DL::Importable.import does mname[0,1].downcase so FooBar becomes fooBar
- if defined?(getConsoleScreenBufferInfo)
- alias_method :GetConsoleScreenBufferInfo, :getConsoleScreenBufferInfo
- module_function :GetConsoleScreenBufferInfo
- end
- if defined?(getStdHandle)
- alias_method :GetStdHandle, :getStdHandle
- module_function :GetStdHandle
- end
- end
- end
-
- CHARACTER_MODE = "Win32API" # For Debugging purposes only.
-
- #
- # Windows savvy getc().
- #
- # *WARNING*: This method ignores <tt>input</tt> and reads one
- # character from +STDIN+!
- #
- def get_character( input = STDIN )
- WinAPI._getch
- end
-
- # We do not define a raw_no_echo_mode for Windows as _getch turns off echo
- def raw_no_echo_mode
- end
-
- def restore_mode
- end
-
- # A Windows savvy method to fetch the console columns, and rows.
- def terminal_size
- format = 'SSSSSssssSS'
- buf = ([0] * format.size).pack(format)
- stdout_handle = WinAPI.GetStdHandle(0xFFFFFFF5)
-
- WinAPI.GetConsoleScreenBufferInfo(stdout_handle, buf)
- _, _, _, _, _,
- left, top, right, bottom, _, _ = buf.unpack(format)
- return right - left + 1, bottom - top + 1
- end
- else # If we're not on Windows try...
- begin
- require "termios" # Unix, first choice termios.
-
- CHARACTER_MODE = "termios" # For Debugging purposes only.
-
- def raw_no_echo_mode
- @state = Termios.getattr(@input)
- new_settings = @state.dup
- new_settings.c_lflag &= ~(Termios::ECHO | Termios::ICANON)
- new_settings.c_cc[Termios::VMIN] = 1
- Termios.setattr(@input, Termios::TCSANOW, new_settings)
- end
-
- def restore_mode
- Termios.setattr(@input, Termios::TCSANOW, @state)
- end
- rescue LoadError # If our first choice fails, try using JLine
- if JRUBY # if we are on JRuby. JLine is bundled with JRuby.
- CHARACTER_MODE = "jline" # For Debugging purposes only.
-
- def terminal_size
- if JRUBY_VERSION =~ /^1.7/
- [ @java_terminal.get_width, @java_terminal.get_height ]
- else
- [ @java_terminal.getTerminalWidth, @java_terminal.getTerminalHeight ]
- end
- end
-
- def raw_no_echo_mode
- @state = @java_console.getEchoCharacter
- @java_console.setEchoCharacter 0
- end
-
- def restore_mode
- @java_console.setEchoCharacter @state
- end
- else # If we are not on JRuby, try ncurses
- begin
- require 'ffi-ncurses'
- CHARACTER_MODE = "ncurses" # For Debugging purposes only.
-
- def raw_no_echo_mode
- FFI::NCurses.initscr
- FFI::NCurses.cbreak
- end
-
- def restore_mode
- FFI::NCurses.endwin
- end
-
- #
- # A ncurses savvy method to fetch the console columns, and rows.
- #
- def terminal_size
- size = [80, 40]
- FFI::NCurses.initscr
- begin
- size = FFI::NCurses.getmaxyx(FFI::NCurses.stdscr).reverse
- ensure
- FFI::NCurses.endwin
- end
- size
- end
- rescue LoadError # Finally, if all else fails, use stty
- # *WARNING*: This requires the external "stty" program!
- CHARACTER_MODE = "stty" # For Debugging purposes only.
-
- def raw_no_echo_mode
- @state = `stty -g`
- system "stty raw -echo -icanon isig"
- end
-
- def restore_mode
- system "stty #{@state}"
- end
- end
- end
- end
-
- # For termios and stty
- if not method_defined?(:terminal_size)
- # A Unix savvy method using stty to fetch the console columns, and rows.
- # ... stty does not work in JRuby
- def terminal_size
- begin
- require "io/console"
- winsize = IO.console.winsize.reverse rescue nil
- return winsize if winsize
- rescue LoadError
- end
-
- if /solaris/ =~ RUBY_PLATFORM and
- `stty` =~ /\brows = (\d+).*\bcolumns = (\d+)/
- [$2, $1].map { |c| x.to_i }
- elsif `stty size` =~ /^(\d+)\s(\d+)$/
- [$2.to_i, $1.to_i]
- else
- [ 80, 24 ]
- end
- end
- end
- end
-
- if not method_defined?(:get_character)
- def get_character( input = STDIN )
- input.getbyte
- end
- end
- end
-end
diff --git a/lib/highline/template_renderer.rb b/lib/highline/template_renderer.rb
new file mode 100644
index 0000000..ea43905
--- /dev/null
+++ b/lib/highline/template_renderer.rb
@@ -0,0 +1,38 @@
+# coding: utf-8
+
+require 'forwardable'
+
+class HighLine
+ class TemplateRenderer
+ extend Forwardable
+
+ def_delegators :@highline, :color, :list, :key, :question
+ def_delegators :@source, :answer_type, :prompt, :header, :answer
+
+ attr_reader :template, :source, :highline
+
+ def initialize(template, source, highline)
+ @template = template
+ @source = source
+ @highline = highline
+ end
+
+ def render
+ template.result(binding)
+ end
+
+ def method_missing(method, *args)
+ "Method #{method} with args #{args.inspect} " +
+ "is not available on #{self.inspect}. " +
+ "Try #{methods(false).sort.inspect}"
+ end
+
+ def menu
+ source
+ end
+
+ def self.const_missing(name)
+ HighLine.const_get(name)
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/highline/terminal.rb b/lib/highline/terminal.rb
new file mode 100755
index 0000000..b23fe65
--- /dev/null
+++ b/lib/highline/terminal.rb
@@ -0,0 +1,50 @@
+# coding: utf-8
+
+# terminal.rb
+#
+# Originally created by James Edward Gray II on 2006-06-14 as
+# system_extensions.rb.
+# Copyright 2006 Gray Productions. All rights reserved.
+#
+# This is Free Software. See LICENSE and COPYING for details.
+
+require "highline/compatibility"
+
+class HighLine
+ class Terminal
+ def self.get_terminal
+ require 'highline/terminal/unix_stty'
+ terminal = HighLine::Terminal::UnixStty.new
+ terminal.initialize_system_extensions
+ terminal
+ end
+
+ def initialize_system_extensions
+ end
+
+ def terminal_size
+ end
+
+ def raw_no_echo_mode
+ end
+
+ def raw_no_echo_mode_exec
+ raw_no_echo_mode
+ begin
+ yield
+ ensure
+ restore_mode
+ end
+ end
+
+ def restore_mode
+ end
+
+ def get_character
+ end
+
+ def jruby?
+ defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
+ end
+ end
+end
diff --git a/lib/highline/terminal/jruby.rb b/lib/highline/terminal/jruby.rb
new file mode 100644
index 0000000..2c1b6da
--- /dev/null
+++ b/lib/highline/terminal/jruby.rb
@@ -0,0 +1,37 @@
+# coding: utf-8
+
+class HighLine
+ module SystemExtensions
+ module JRuby
+ def initialize_system_extensions
+ require 'java'
+ require 'readline'
+ if JRUBY_VERSION =~ /^1.7/
+ java_import 'jline.console.ConsoleReader'
+
+ input = @input && @input.to_inputstream
+ output = @output && @output.to_outputstream
+
+ @java_console = ConsoleReader.new(input, output)
+ @java_console.set_history_enabled(false)
+ @java_console.set_bell_enabled(true)
+ @java_console.set_pagination_enabled(false)
+ @java_terminal = @java_console.getTerminal
+ elsif JRUBY_VERSION =~ /^1.6/
+ java_import 'java.io.OutputStreamWriter'
+ java_import 'java.nio.channels.Channels'
+ java_import 'jline.ConsoleReader'
+ java_import 'jline.Terminal'
+
+ @java_input = Channels.newInputStream(@input.to_channel)
+ @java_output = OutputStreamWriter.new(Channels.newOutputStream(@output.to_channel))
+ @java_terminal = Terminal.getTerminal
+ @java_console = ConsoleReader.new(@java_input, @java_output)
+ @java_console.setUseHistory(false)
+ @java_console.setBellEnabled(true)
+ @java_console.setUsePagination(false)
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/highline/terminal/jruby_jline.rb b/lib/highline/terminal/jruby_jline.rb
new file mode 100644
index 0000000..8e9a47d
--- /dev/null
+++ b/lib/highline/terminal/jruby_jline.rb
@@ -0,0 +1,35 @@
+# coding: utf-8
+
+class HighLine
+ module SystemExtensions
+ module JRubyJLine
+ CHARACTER_MODE = "jline" # For Debugging purposes only.
+
+ def terminal_size
+ if JRUBY_VERSION =~ /^1.7/
+ [ @java_terminal.get_width, @java_terminal.get_height ]
+ else
+ [ @java_terminal.getTerminalWidth, @java_terminal.getTerminalHeight ]
+ end
+ end
+
+ def raw_no_echo_mode
+ @state = @java_console.getEchoCharacter
+ @java_console.setEchoCharacter 0
+ end
+
+ def restore_mode
+ @java_console.setEchoCharacter @state
+ end
+
+ # Saving this legacy JRuby code for future reference
+ def get_line(question, highline, options={})
+ statement = highline.render_statement(question)
+ raw_answer = @java_console.readLine(statement, nil)
+
+ raise EOFError, "The input stream is exhausted." if raw_answer.nil? and
+ highline.track_eof?
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/highline/terminal/ncurses.rb b/lib/highline/terminal/ncurses.rb
new file mode 100644
index 0000000..cb2b593
--- /dev/null
+++ b/lib/highline/terminal/ncurses.rb
@@ -0,0 +1,33 @@
+# coding: utf-8
+
+class HighLine
+ module SystemExtensions
+ module NCurses
+ require 'ffi-ncurses'
+ CHARACTER_MODE = "ncurses" # For Debugging purposes only.
+
+ def raw_no_echo_mode
+ FFI::NCurses.initscr
+ FFI::NCurses.cbreak
+ end
+
+ def restore_mode
+ FFI::NCurses.endwin
+ end
+
+ #
+ # A ncurses savvy method to fetch the console columns, and rows.
+ #
+ def terminal_size
+ size = [80, 40]
+ FFI::NCurses.initscr
+ begin
+ size = FFI::NCurses.getmaxyx(FFI::NCurses.stdscr).reverse
+ ensure
+ FFI::NCurses.endwin
+ end
+ size
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/highline/terminal/stty.rb b/lib/highline/terminal/stty.rb
new file mode 100644
index 0000000..168f8c5
--- /dev/null
+++ b/lib/highline/terminal/stty.rb
@@ -0,0 +1,19 @@
+# coding: utf-8
+
+class HighLine
+ module SystemExtensions
+ module Stty
+ # *WARNING*: This requires the external "stty" program!
+ CHARACTER_MODE = "stty" # For Debugging purposes only.
+
+ def raw_no_echo_mode
+ @state = `stty -g`
+ system "stty raw -echo -icanon isig"
+ end
+
+ def restore_mode
+ system "stty #{@state}"
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/highline/terminal/unix_stty.rb b/lib/highline/terminal/unix_stty.rb
new file mode 100644
index 0000000..7914c12
--- /dev/null
+++ b/lib/highline/terminal/unix_stty.rb
@@ -0,0 +1,93 @@
+# coding: utf-8
+
+class HighLine
+ class Terminal::UnixStty < Terminal
+
+ # A Unix savvy method using stty to fetch the console columns, and rows.
+ # ... stty does not work in JRuby
+ def terminal_size
+ begin
+ require "io/console"
+ winsize = IO.console.winsize.reverse rescue nil
+ return winsize if winsize
+ rescue LoadError
+ end
+
+ if /solaris/ =~ RUBY_PLATFORM and
+ `stty` =~ /\brows = (\d+).*\bcolumns = (\d+)/
+ [$2, $1].map { |c| x.to_i }
+ elsif `stty size` =~ /^(\d+)\s(\d+)$/
+ [$2.to_i, $1.to_i]
+ else
+ [ 80, 24 ]
+ end
+ end
+
+ # *WARNING*: This requires the external "stty" program!
+ CHARACTER_MODE = "unix_stty" # For Debugging purposes only.
+
+ def raw_no_echo_mode
+ @state = `stty -g`
+ system "stty raw -echo -icanon isig"
+ end
+
+ def restore_mode
+ system "stty #{@state}"
+ end
+
+ def get_character( input = STDIN )
+ input.getc
+ end
+
+ def character_mode
+ "unix_stty"
+ end
+
+ def get_line(question, highline, options={})
+ raw_answer =
+ if question.readline
+ get_line_with_readline(question, highline, options={})
+ else
+ get_line_default(highline)
+ end
+
+ question.format_answer(raw_answer)
+ end
+
+ def get_line_with_readline(question, highline, options={})
+ require "readline" # load only if needed
+
+ question_string = highline.render_statement(question)
+
+ raw_answer = readline_read(question_string, question)
+
+ if raw_answer.nil? and highline.track_eof?
+ raise EOFError, "The input stream is exhausted."
+ end
+
+ raw_answer || ""
+ end
+
+ def readline_read(string, question)
+ # prep auto-completion
+ Readline.completion_proc = lambda do |string|
+ question.selection.grep(/\A#{Regexp.escape(string)}/)
+ end
+
+ # work-around ugly readline() warnings
+ old_verbose = $VERBOSE
+ $VERBOSE = nil
+ raw_answer = Readline.readline(string, true)
+
+ $VERBOSE = old_verbose
+
+ raw_answer
+ end
+
+ def get_line_default(highline)
+ raise EOFError, "The input stream is exhausted." if highline.track_eof? and
+ highline.input.eof?
+ highline.input.gets
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/highline/terminal/unix_termios.rb b/lib/highline/terminal/unix_termios.rb
new file mode 100644
index 0000000..3a0a799
--- /dev/null
+++ b/lib/highline/terminal/unix_termios.rb
@@ -0,0 +1,23 @@
+# coding: utf-8
+
+class HighLine
+ module SystemExtensions
+ module UnixTermios
+ require "termios" # Unix, first choice termios.
+
+ CHARACTER_MODE = "termios" # For Debugging purposes only.
+
+ def raw_no_echo_mode
+ @state = Termios.getattr(@input)
+ new_settings = @state.dup
+ new_settings.c_lflag &= ~(Termios::ECHO | Termios::ICANON)
+ new_settings.c_cc[Termios::VMIN] = 1
+ Termios.setattr(@input, Termios::TCSANOW, new_settings)
+ end
+
+ def restore_mode
+ Termios.setattr(@input, Termios::TCSANOW, @state)
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/highline/terminal/windows.rb b/lib/highline/terminal/windows.rb
new file mode 100644
index 0000000..81b1d67
--- /dev/null
+++ b/lib/highline/terminal/windows.rb
@@ -0,0 +1,38 @@
+# coding: utf-8
+
+class HighLine
+ module SystemExtensions
+ module Windows
+ CHARACTER_MODE = "Win32API" # For Debugging purposes only.
+
+ #
+ # Windows savvy getc().
+ #
+ # *WARNING*: This method ignores <tt>input</tt> and reads one
+ # character from +STDIN+!
+ #
+ def get_character( input = STDIN )
+ WinAPI._getch
+ end
+
+ # We do not define a raw_no_echo_mode for Windows as _getch turns off echo
+ def raw_no_echo_mode
+ end
+
+ def restore_mode
+ end
+
+ # A Windows savvy method to fetch the console columns, and rows.
+ def terminal_size
+ format = 'SSSSSssssSS'
+ buf = ([0] * format.size).pack(format)
+ stdout_handle = WinAPI.GetStdHandle(0xFFFFFFF5)
+
+ WinAPI.GetConsoleScreenBufferInfo(stdout_handle, buf)
+ _, _, _, _, _,
+ left, top, right, bottom, _, _ = buf.unpack(format)
+ return right - left + 1, bottom - top + 1
+ end
+ end
+ end
+end
diff --git a/lib/highline/terminal/windows_dl_import.rb b/lib/highline/terminal/windows_dl_import.rb
new file mode 100644
index 0000000..721f048
--- /dev/null
+++ b/lib/highline/terminal/windows_dl_import.rb
@@ -0,0 +1,37 @@
+# coding: utf-8
+
+class HighLine
+ module SystemExtensions
+ module WindowsDlImport
+ require "dl/import"
+
+ module WinAPI
+ if defined?(DL::Importer)
+ # Ruby 1.9
+ extend DL::Importer
+ else
+ # Ruby 1.8
+ extend DL::Importable
+ end
+ begin
+ dlload "msvcrt", "kernel32"
+ rescue DL::DLError
+ dlload "crtdll", "kernel32"
+ end
+ extern "unsigned long _getch()"
+ extern "unsigned long GetConsoleScreenBufferInfo(unsigned long, void*)"
+ extern "unsigned long GetStdHandle(unsigned long)"
+
+ # Ruby 1.8 DL::Importable.import does mname[0,1].downcase so FooBar becomes fooBar
+ if defined?(getConsoleScreenBufferInfo)
+ alias_method :GetConsoleScreenBufferInfo, :getConsoleScreenBufferInfo
+ module_function :GetConsoleScreenBufferInfo
+ end
+ if defined?(getStdHandle)
+ alias_method :GetStdHandle, :getStdHandle
+ module_function :GetStdHandle
+ end
+ end
+ end
+ end
+end
diff --git a/lib/highline/terminal/windows_fiddle.rb b/lib/highline/terminal/windows_fiddle.rb
new file mode 100644
index 0000000..7e0c844
--- /dev/null
+++ b/lib/highline/terminal/windows_fiddle.rb
@@ -0,0 +1,32 @@
+# coding: utf-8
+
+class HighLine
+ module SystemExtensions
+ module WindowsFiddle
+ require "fiddle"
+
+ module WinAPI
+ include Fiddle
+ Handle = RUBY_VERSION >= "2.0.0" ? Fiddle::Handle : DL::Handle
+ Kernel32 = Handle.new("kernel32")
+ Crt = Handle.new("msvcrt") rescue Handle.new("crtdll")
+
+ def self._getch
+ @@_m_getch ||= Function.new(Crt["_getch"], [], TYPE_INT)
+ @@_m_getch.call
+ end
+
+ def self.GetStdHandle(handle_type)
+ @@get_std_handle ||= Function.new(Kernel32["GetStdHandle"], [-TYPE_INT], -TYPE_INT)
+ @@get_std_handle.call(handle_type)
+ end
+
+ def self.GetConsoleScreenBufferInfo(cons_handle, lp_buffer)
+ @@get_console_screen_buffer_info ||=
+ Function.new(Kernel32["GetConsoleScreenBufferInfo"], [TYPE_LONG, TYPE_VOIDP], TYPE_INT)
+ @@get_console_screen_buffer_info.call(cons_handle, lp_buffer)
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/highline/version.rb b/lib/highline/version.rb
index d01247e..288e4fa 100644
--- a/lib/highline/version.rb
+++ b/lib/highline/version.rb
@@ -1,4 +1,6 @@
+# coding: utf-8
+
class HighLine
# The version of the installed library.
- VERSION = "1.7.1".freeze
+ VERSION = "2.0.0-develop.1".freeze
end
diff --git a/lib/highline/wrapper.rb b/lib/highline/wrapper.rb
new file mode 100644
index 0000000..98f713f
--- /dev/null
+++ b/lib/highline/wrapper.rb
@@ -0,0 +1,43 @@
+# coding: utf-8
+
+class HighLine
+ module Wrapper
+
+ #
+ # Wrap a sequence of _lines_ at _wrap_at_ characters per line. Existing
+ # newlines will not be affected by this process, but additional newlines
+ # may be added.
+ #
+ def self.wrap(text, wrap_at)
+ return text unless wrap_at
+ wrap_at = Integer(wrap_at)
+
+ wrapped = [ ]
+ text.each_line do |line|
+ # take into account color escape sequences when wrapping
+ wrap_at = wrap_at + (line.length - actual_length(line))
+ while line =~ /([^\n]{#{wrap_at + 1},})/
+ search = $1.dup
+ replace = $1.dup
+ if index = replace.rindex(" ", wrap_at)
+ replace[index, 1] = "\n"
+ replace.sub!(/\n[ \t]+/, "\n")
+ line.sub!(search, replace)
+ else
+ line[$~.begin(1) + wrap_at, 0] = "\n"
+ end
+ end
+ wrapped << line
+ end
+ return wrapped.join
+ end
+
+ #
+ # Returns the length of the passed +string_with_escapes+, minus and color
+ # sequence escapes.
+ #
+ def self.actual_length( string_with_escapes )
+ string_with_escapes.to_s.gsub(/\e\[\d{1,2}m/, "").length
+ end
+ end
+end \ No newline at end of file
diff --git a/test/string_methods.rb b/test/string_methods.rb
index 0ae2c5c..9c61359 100644
--- a/test/string_methods.rb
+++ b/test/string_methods.rb
@@ -1,3 +1,6 @@
+#!/usr/bin/env ruby
+# coding: utf-8
+
# string_methods.rb
#
# Created by Richard LeBer 2011-06-27
diff --git a/test/test_color_scheme.rb b/test/test_color_scheme.rb
index 7cb57c4..388c0ea 100644
--- a/test/test_color_scheme.rb
+++ b/test/test_color_scheme.rb
@@ -1,3 +1,6 @@
+#!/usr/bin/env ruby
+# coding: utf-8
+
# tc_color_scheme.rb
#
# Created by Jeremy Hinegardner on 2007-01-24.
@@ -6,6 +9,7 @@
# This is Free Software. See LICENSE and COPYING for details.
require "minitest/autorun"
+require "test_helper"
require "highline"
require "stringio"
diff --git a/test/test_helper.rb b/test/test_helper.rb
new file mode 100644
index 0000000..ac8e809
--- /dev/null
+++ b/test/test_helper.rb
@@ -0,0 +1,11 @@
+#!/usr/bin/env ruby
+# coding: utf-8
+
+require 'simplecov'
+SimpleCov.start do
+ add_filter "test_"
+end
+
+require "codeclimate-test-reporter"
+CodeClimate::TestReporter.start
+
diff --git a/test/test_highline.rb b/test/test_highline.rb
index 3d64870..b3e822b 100755
--- a/test/test_highline.rb
+++ b/test/test_highline.rb
@@ -1,4 +1,6 @@
-# encoding: utf-8
+#!/usr/bin/env ruby
+# coding: utf-8
+
# tc_highline.rb
#
# Created by James Edward Gray II on 2005-04-26.
@@ -7,12 +9,15 @@
# This is Free Software. See LICENSE and COPYING for details.
require "minitest/autorun"
+require "test_helper"
require "highline"
require "stringio"
require "readline"
require "tempfile"
+
+=begin
if HighLine::CHARACTER_MODE == "Win32API"
class HighLine
# Override Windows' character reading so it's not tied to STDIN.
@@ -21,6 +26,7 @@ if HighLine::CHARACTER_MODE == "Win32API"
end
end
end
+=end
class TestHighLine < Minitest::Test
def setup
@@ -74,6 +80,25 @@ class TestHighLine < Minitest::Test
assert_raises(EOFError) { @terminal.ask("Any input left? ", String) }
end
+ def test_ask_string_converting
+ name = "James Edward Gray II"
+ @input << name << "\n"
+ @input.rewind
+
+ answer = @terminal.ask("What is your name? ", String)
+
+ assert_instance_of HighLine::String, answer
+
+ @input.rewind
+
+ answer = @terminal.ask("What is your name? ", HighLine::String)
+
+ assert_instance_of HighLine::String, answer
+
+ assert_raises(EOFError) { @terminal.ask("Any input left? ", HighLine::String) }
+ end
+
+
def test_indent
text = "Testing...\n"
@terminal.indent_level=1
@@ -92,28 +117,29 @@ class TestHighLine < Minitest::Test
assert_equal(' '*10+text, @output.string)
@output.truncate(@output.rewind)
+ @terminal.indent_level=0
@terminal.indent_size=4
@terminal.indent {
- @terminal.say(text)
+ @terminal.say(text)
}
assert_equal(' '*4+text, @output.string)
@output.truncate(@output.rewind)
@terminal.indent_size=2
@terminal.indent(3) { |t|
- t.say(text)
+ t.say(text)
}
assert_equal(' '*6+text, @output.string)
@output.truncate(@output.rewind)
@terminal.indent { |t|
+ t.indent {
t.indent {
- t.indent {
- t.indent { |tt|
- tt.say(text)
- }
- }
+ t.indent { |tt|
+ tt.say(text)
+ }
}
+ }
}
assert_equal(' '*8+text, @output.string)
@@ -449,7 +475,7 @@ class TestHighLine < Minitest::Test
@input.rewind
answer = @terminal.ask("Enter a filename: ") do |q|
- q.confirm = "Are you sure you want to overwrite <%= @answer %>? "
+ q.confirm = "Are you sure you want to overwrite <%= answer %>? "
q.responses[:ask_on_error] = :question
end
assert_equal("save.txt", answer)
@@ -465,7 +491,7 @@ class TestHighLine < Minitest::Test
@output.truncate(@output.rewind)
answer = @terminal.ask("Enter a filename: ") do |q|
- q.confirm = "Are you sure you want to overwrite <%= @answer %>? "
+ q.confirm = "Are you sure you want to overwrite <%= answer %>? "
end
assert_equal("junk.txt", answer)
assert_equal( "Enter a filename: " +
@@ -588,7 +614,7 @@ class TestHighLine < Minitest::Test
q.glob = "*.rb"
end
assert_instance_of(File, file)
- assert_equal("# encoding: utf-8\n", file.gets)
+ assert_equal("#!/usr/bin/env ruby\n", file.gets)
file.close
@input.rewind
@@ -601,7 +627,7 @@ class TestHighLine < Minitest::Test
assert_equal(File.size(__FILE__), pathname.size)
end
- def test_gather
+ def test_gather_with_integer
@input << "James\nDana\nStorm\nGypsy\n\n"
@input.rewind
@@ -611,27 +637,33 @@ class TestHighLine < Minitest::Test
assert_equal(%w{James Dana Storm Gypsy}, answers)
assert_equal("\n", @input.gets)
assert_equal("Enter four names:\n", @output.string)
+ end
+ def test_gather_with_an_empty_string
+ @input << "James\nDana\nStorm\nGypsy\n\n"
@input.rewind
answers = @terminal.ask("Enter four names:") do |q|
q.gather = ""
end
assert_equal(%w{James Dana Storm Gypsy}, answers)
+ end
+ def test_gather_with_regexp
+ @input << "James\nDana\nStorm\nGypsy\n\n"
@input.rewind
answers = @terminal.ask("Enter four names:") do |q|
q.gather = /^\s*$/
end
assert_equal(%w{James Dana Storm Gypsy}, answers)
+ end
- @input.truncate(@input.rewind)
+ def test_gather_with_hash
@input << "29\n49\n30\n"
@input.rewind
- @output.truncate(@output.rewind)
- answers = @terminal.ask("<%= @key %>: ", Integer) do |q|
+ answers = @terminal.ask("<%= key %>: ", Integer) do |q|
q.gather = { "Age" => 0, "Wife's Age" => 0, "Father's Age" => 0}
end
assert_equal( { "Age" => 29, "Wife's Age" => 30, "Father's Age" => 49},
@@ -670,7 +702,7 @@ class TestHighLine < Minitest::Test
@input.rewind
@output.truncate(@output.rewind)
- answer = @terminal.ask("<%= @key %>: ") do |q|
+ answer = @terminal.ask("<%= key %>: ") do |q|
q.verify_match = true
q.gather = {"Enter a password" => '', "Please type it again" => ''}
end
@@ -681,7 +713,7 @@ class TestHighLine < Minitest::Test
@input.rewind
@output.truncate(@output.rewind)
- answer = @terminal.ask("<%= @key %>: ") do |q|
+ answer = @terminal.ask("<%= key %>: ") do |q|
q.verify_match = true
q.responses[:mismatch] = 'Typing mismatch!'
q.responses[:ask_on_error] = ''
@@ -856,8 +888,8 @@ class TestHighLine < Minitest::Test
end
def test_mode
- assert(%w[Win32API termios ncurses stty jline].include?(HighLine::CHARACTER_MODE),
- "#{HighLine::CHARACTER_MODE} not in list")
+ assert(%w[Win32API termios ncurses stty unix_stty jline].include?(@terminal.terminal.character_mode),
+ "#{@terminal.terminal.character_mode} not in list")
end
class NameClass
@@ -963,21 +995,6 @@ class TestHighLine < Minitest::Test
assert_equal("Type: ****\n", @output.string)
assert_equal("maçã", answer)
end
-
- def test_paging
- @terminal.page_at = 22
-
- @input << "\n\n"
- @input.rewind
-
- @terminal.say((1..50).map { |n| "This is line #{n}.\n"}.join)
- assert_equal( (1..22).map { |n| "This is line #{n}.\n"}.join +
- "\n-- press enter/return to continue or q to stop -- \n\n" +
- (23..44).map { |n| "This is line #{n}.\n"}.join +
- "\n-- press enter/return to continue or q to stop -- \n\n" +
- (45..50).map { |n| "This is line #{n}.\n"}.join,
- @output.string )
- end
def test_range_requirements
@input << "112\n-541\n28\n"
@@ -1096,8 +1113,8 @@ class TestHighLine < Minitest::Test
answer = @terminal.ask("Tell me your age.", Integer) do |q|
q.in = 0..105
- q.responses[:not_in_range] = "Need a <%= @question.answer_type %>" +
- " <%= @question.expected_range %>."
+ q.responses[:not_in_range] = "Need a <%= question.answer_type %>" +
+ " <%= question.expected_range %>."
end
assert_equal(28, answer)
assert_equal( "Tell me your age.\n" +
@@ -1163,8 +1180,8 @@ class TestHighLine < Minitest::Test
end
def test_terminal_size
- assert_instance_of(Fixnum, @terminal.terminal_size[0])
- assert_instance_of(Fixnum, @terminal.terminal_size[1])
+ assert_instance_of(Fixnum, @terminal.terminal.terminal_size[0])
+ assert_instance_of(Fixnum, @terminal.terminal.terminal_size[1])
end
def test_type_conversion
@@ -1304,40 +1321,6 @@ class TestHighLine < Minitest::Test
assert_equal(" A lot\tof \t space\t \there! \n", answer)
end
- def test_wrap
- @terminal.wrap_at = 80
-
- @terminal.say("This is a very short line.")
- assert_equal("This is a very short line.\n", @output.string)
-
- @output.truncate(@output.rewind)
-
- @terminal.say( "This is a long flowing paragraph meant to span " +
- "several lines. This text should definitely be " +
- "wrapped at the set limit, in the result. Your code " +
- "does well with things like this.\n\n" +
- " * This is a simple embedded list.\n" +
- " * You're code should not mess with this...\n" +
- " * Because it's already formatted correctly and " +
- "does not\n" +
- " exceed the limit!" )
- assert_equal( "This is a long flowing paragraph meant to span " +
- "several lines. This text should\n" +
- "definitely be wrapped at the set limit, in the " +
- "result. Your code does well with\n" +
- "things like this.\n\n" +
- " * This is a simple embedded list.\n" +
- " * You're code should not mess with this...\n" +
- " * Because it's already formatted correctly and does " +
- "not\n" +
- " exceed the limit!\n", @output.string )
-
- @output.truncate(@output.rewind)
-
- @terminal.say("-=" * 50)
- assert_equal(("-=" * 40 + "\n") + ("-=" * 10 + "\n"), @output.string)
- end
-
def test_track_eof
assert_raises(EOFError) { @terminal.ask("Any input left? ") }
@@ -1356,6 +1339,6 @@ class TestHighLine < Minitest::Test
refute_nil(HighLine::VERSION)
assert_instance_of(String, HighLine::VERSION)
assert(HighLine::VERSION.frozen?)
- assert_match(/\A\d+\.\d+\.\d+\Z/, HighLine::VERSION)
+ assert_match(/\A\d+\.\d+\.\d+(-.*)?/, HighLine::VERSION)
end
end
diff --git a/test/test_import.rb b/test/test_import.rb
index aa68366..4bac518 100644
--- a/test/test_import.rb
+++ b/test/test_import.rb
@@ -1,3 +1,6 @@
+#!/usr/bin/env ruby
+# coding: utf-8
+
# tc_import.rb
#
# Created by James Edward Gray II on 2005-04-26.
@@ -6,6 +9,7 @@
# This is Free Software. See LICENSE and COPYING for details.
require "minitest/autorun"
+require "test_helper"
require "highline/import"
require "stringio"
diff --git a/test/test_list.rb b/test/test_list.rb
new file mode 100644
index 0000000..c363642
--- /dev/null
+++ b/test/test_list.rb
@@ -0,0 +1,61 @@
+#!/usr/bin/env ruby
+# coding: utf-8
+
+require "minitest/autorun"
+require "test_helper"
+
+require "highline/list"
+
+class TestHighLineList < Minitest::Test
+ def setup
+ @items = [ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j" ]
+ end
+
+ def test_in_2_cols
+ list_in_two_cols =
+ [ [ "a", "b" ],
+ [ "c", "d" ],
+ [ "e", "f" ],
+ [ "g", "h" ],
+ [ "i", "j" ] ]
+
+ highline_list = HighLine::List.new(@items, cols: 2)
+
+ assert_equal list_in_two_cols, highline_list.list
+ end
+
+ def test_in_2_cols_col_down
+ col_down_list =
+ [ [ "a", "f"],
+ [ "b", "g"],
+ [ "c", "h"],
+ [ "d", "i"],
+ [ "e", "j"] ]
+
+ highline_list = HighLine::List.new(@items, cols: 2, col_down: true)
+
+ assert_equal col_down_list, highline_list.list
+ end
+
+ def test_in_2_cols_transposed
+ transposed_list =
+ [ [ "a", "c", "e", "g", "i" ],
+ [ "b", "d", "f", "h", "j" ] ]
+
+ highline_list = HighLine::List.new(@items, cols: 2, transpose: true)
+
+ assert_equal transposed_list, highline_list.list
+ end
+
+ def test_in_3_cols
+ list_in_three_cols =
+ [ [ "a", "b", "c" ],
+ [ "d", "e", "f" ],
+ [ "g", "h", "i" ],
+ [ "j" ] ]
+
+ highline_list = HighLine::List.new(@items, cols: 3)
+
+ assert_equal list_in_three_cols, highline_list.list
+ end
+end \ No newline at end of file
diff --git a/test/test_menu.rb b/test/test_menu.rb
index 2b8fddf..de01115 100644
--- a/test/test_menu.rb
+++ b/test/test_menu.rb
@@ -1,4 +1,6 @@
+#!/usr/bin/env ruby
# coding: utf-8
+
# tc_menu.rb
#
# Created by Gregory Thomas Brown on 2005-05-10.
@@ -7,6 +9,7 @@
# This is Free Software. See LICENSE and COPYING for details.
require "minitest/autorun"
+require "test_helper"
require "highline"
require "stringio"
@@ -202,7 +205,7 @@ class TestMenu < Minitest::Test
@output.truncate(@output.rewind)
@terminal.choose(:load, :save, :quit) do |menu|
- menu.layout = '<%= list(@menu) %>File Menu: '
+ menu.layout = '<%= list(menu) %>File Menu: '
end
assert_equal("1. load\n2. save\n3. quit\nFile Menu: ", @output.string)
end
diff --git a/test/test_paginator.rb b/test/test_paginator.rb
new file mode 100644
index 0000000..5f89899
--- /dev/null
+++ b/test/test_paginator.rb
@@ -0,0 +1,31 @@
+#!/usr/bin/env ruby
+# coding: utf-8
+
+require "minitest/autorun"
+require "test_helper"
+
+require "highline"
+
+class TestHighLinePaginator < Minitest::Test
+ def setup
+ HighLine.reset
+ @input = StringIO.new
+ @output = StringIO.new
+ @terminal = HighLine.new(@input, @output)
+ end
+
+ def test_paging
+ @terminal.page_at = 22
+
+ @input << "\n\n"
+ @input.rewind
+
+ @terminal.say((1..50).map { |n| "This is line #{n}.\n"}.join)
+ assert_equal( (1..22).map { |n| "This is line #{n}.\n"}.join +
+ "\n-- press enter/return to continue or q to stop -- \n\n" +
+ (23..44).map { |n| "This is line #{n}.\n"}.join +
+ "\n-- press enter/return to continue or q to stop -- \n\n" +
+ (45..50).map { |n| "This is line #{n}.\n"}.join,
+ @output.string )
+ end
+end \ No newline at end of file
diff --git a/test/test_simulator.rb b/test/test_simulator.rb
new file mode 100644
index 0000000..15691d9
--- /dev/null
+++ b/test/test_simulator.rb
@@ -0,0 +1,25 @@
+require "minitest/autorun"
+require "test_helper"
+
+require "highline/import"
+require "highline/simulate"
+
+class SimulatorTest < Minitest::Test
+ def setup
+ input = StringIO.new
+ output = StringIO.new
+ $terminal = HighLine.new(input, output)
+ end
+
+ def test_simulator
+ HighLine::Simulate.with("Bugs Bunny", "18") do
+ name = ask("What is your name?")
+
+ assert_equal "Bugs Bunny", name
+
+ age = ask("What is your age?")
+
+ assert_equal "18", age
+ end
+ end
+end \ No newline at end of file
diff --git a/test/test_string_extension.rb b/test/test_string_extension.rb
index d774e11..2527da5 100644
--- a/test/test_string_extension.rb
+++ b/test/test_string_extension.rb
@@ -1,3 +1,6 @@
+#!/usr/bin/env ruby
+# coding: utf-8
+
# tc_string_extension.rb
#
# Created by Richard LeBer 2011-06-27
@@ -5,6 +8,7 @@
# This is Free Software. See LICENSE and COPYING for details.
require "minitest/autorun"
+require "test_helper"
require "highline"
require "stringio"
diff --git a/test/test_string_highline.rb b/test/test_string_highline.rb
index 63e5cfc..ad450af 100644
--- a/test/test_string_highline.rb
+++ b/test/test_string_highline.rb
@@ -1,3 +1,6 @@
+#!/usr/bin/env ruby
+# coding: utf-8
+
# tc_highline_string.rb
#
# Created by Richard LeBer 2011-06-27
@@ -5,6 +8,7 @@
# This is Free Software. See LICENSE and COPYING for details.
require "minitest/autorun"
+require "test_helper"
require "highline"
require "stringio"
diff --git a/test/test_style.rb b/test/test_style.rb
index 3c3cc14..ccba936 100755
--- a/test/test_style.rb
+++ b/test/test_style.rb
@@ -1,3 +1,6 @@
+#!/usr/bin/env ruby
+# coding: utf-8
+
# tc_style.rb
#
# Created by Richard LeBer on 2011-06-11.
@@ -5,6 +8,7 @@
# This is Free Software. See LICENSE and COPYING for details.
require "minitest/autorun"
+require "test_helper"
require "highline"
require "stringio"
diff --git a/test/test_wrapper.rb b/test/test_wrapper.rb
new file mode 100644
index 0000000..6afcdff
--- /dev/null
+++ b/test/test_wrapper.rb
@@ -0,0 +1,189 @@
+#!/usr/bin/env ruby
+# coding: utf-8
+
+require "minitest/autorun"
+require "test_helper"
+
+require "highline/wrapper"
+
+class TestHighLineWrapper < Minitest::Test
+ def setup
+ @wrap_at = 80
+ end
+
+ def wrap(text)
+ HighLine::Wrapper.wrap text, @wrap_at
+ end
+
+ def test_dont_wrap_if_line_is_shorter_than_wrap_at
+ wrapped = wrap("This is a very short line.\n")
+ assert_equal "This is a very short line.\n", wrapped
+ end
+
+ def test_wrap_long_lines_correctly
+ long_line =
+ "This is a long flowing paragraph meant to span " +
+ "several lines. This text should definitely be " +
+ "wrapped at the set limit, in the result. Your code " +
+ "does well with things like this.\n\n"
+
+ wrapped_long_line =
+ "This is a long flowing paragraph meant to span " +
+ "several lines. This text should\n" +
+
+ "definitely be wrapped at the set limit, in the " +
+ "result. Your code does well with\n" +
+
+ "things like this.\n\n"
+
+ wrapped = wrap(long_line)
+ assert_equal wrapped_long_line, wrapped
+ end
+
+ def test_dont_wrap_already_well_wrapped_text
+ well_formatted_text =
+ " * This is a simple embedded list.\n" +
+ " * You're code should not mess with this...\n" +
+ " * Because it's already formatted correctly and does not\n" +
+ " exceed the limit!\n"
+
+ wrapped = wrap(well_formatted_text)
+ assert_equal well_formatted_text, wrapped
+ end
+
+ def test_wrap_single_word_longer_than_wrap_at
+ wrapped = wrap("-=" * 50)
+ assert_equal(("-=" * 40 + "\n") + ("-=" * 10), wrapped)
+ end
+
+ def test_wrap_plain_text
+ line = "123 567 901 345"
+
+ 1.upto(25) do |wrap_at|
+ wrapped = HighLine::Wrapper.wrap(line, wrap_at)
+
+ case wrap_at
+ when 1
+ assert_equal "1\n2\n3\n5\n6\n7\n9\n0\n1\n3\n4\n5", wrapped
+ when 2
+ assert_equal "12\n3\n56\n7\n90\n1\n34\n5", wrapped
+ when 3..6
+ assert_equal "123\n567\n901\n345", wrapped
+ when 7..10
+ assert_equal "123 567\n901 345", wrapped
+ when 11..14
+ assert_equal "123 567 901\n345", wrapped
+ when 15..25
+ assert_equal "123 567 901 345", wrapped
+ end
+ end
+ end
+
+ def test_wrap_whole_colored_text
+ skip "TODO: Implement whole colored text wrapping!"
+ line = "\e[31m123 567 901 345\e[0m"
+
+ 1.upto(25) do |wrap_at|
+ wrapped = HighLine::Wrapper.wrap(line, wrap_at)
+
+ case wrap_at
+ when 1
+ assert_equal "\e[31m1\n2\n3\n5\n6\n7\n9\n0\n1\n3\n4\n5\e[0m", wrapped
+ when 2
+ assert_equal "\e[31m12\n3\n56\n7\n90\n1\n34\n5\e[0m", wrapped
+ when 3..6
+ assert_equal "\e[31m123\n567\n901\n345\e[0m", wrapped
+ when 7..10
+ assert_equal "\e[31m123 567\n901 345\e[0m", wrapped
+ when 11..14
+ assert_equal "\e[31m123 567 901\n345\e[0m", wrapped
+ when 15..25
+ assert_equal "\e[31m123 567 901 345\e[0m", wrapped
+ end
+ end
+ end
+
+ def test_wrap_partially_colored_text
+ skip "TODO: Implement middle colored text wrapping!"
+ line = "123 567 \e[31m901\e[0m 345"
+
+ 1.upto(25) do |wrap_at|
+ wrapped = HighLine::Wrapper.wrap(line, wrap_at)
+
+ case wrap_at
+ when 1
+ assert_equal "1\n2\n3\n5\n6\n7\n\e[31m9\n0\n1\e[0m\n3\n4\n5", wrapped
+ when 2
+ assert_equal "12\n3\n56\n7\n\e[31m90\n1\e[0m\n34\n5", wrapped
+ when 3..6
+ assert_equal "123\n567\n\e[31m901\e[0m\n345", wrapped
+ when 7..10
+ assert_equal "123 567\n\e[31m901\e[0m 345", wrapped
+ when 11..14
+ assert_equal "123 567 \e[31m901\e[0m\n345", wrapped
+ when 15..25
+ assert_equal "123 567 \e[31m901\e[0m 345", wrapped
+ end
+ end
+ end
+
+ def test_wrap_text_with_partially_colored_word_in_the_middle
+ skip "TODO: Implement middle partially colored text wrapping!"
+ line = "123 567 9\e[31m0\e[0m1 345"
+
+ 1.upto(25) do |wrap_at|
+ wrapped = HighLine::Wrapper.wrap(line, wrap_at)
+
+ case wrap_at
+ when 1
+ assert_equal "1\n2\n3\n5\n6\n7\n9\n\e[31m0\e[0m\n1\n3\n4\n5", wrapped
+ when 2
+ assert_equal "12\n3\n56\n7\n9\e[31m0\e[0m\n1\n34\n5", wrapped
+ when 3..6
+ assert_equal "123\n567\n9\e[31m0\e[0m1\n345", wrapped
+ when 7..10
+ assert_equal "123 567\n9\e[31m0\e[0m1 345", wrapped
+ when 11..14
+ assert_equal "123 567 9\e[31m0\e[0m1\n345", wrapped
+ when 15..25
+ assert_equal "123 567 9\e[31m0\e[0m1 345", wrapped
+ end
+ end
+ end
+
+ def test_wrap_when_multibyte_characters_present
+ line_ascii = "Sera um passaro?"
+ line_utf8 = "Será um pássaro?"
+
+ assert_equal 16, line_ascii.size
+ assert_equal 16, line_ascii.bytesize
+
+ assert_equal 16, line_utf8.size
+ assert_equal 18, line_utf8.bytesize
+
+ 1.upto(18) do |wrap_at|
+ wrapped = HighLine::Wrapper.wrap(line_utf8, wrap_at)
+
+ case wrap_at
+ when 1
+ assert_equal "S\ne\nr\ná\nu\nm\np\ná\ns\ns\na\nr\no\n?", wrapped
+ when 2
+ assert_equal "Se\nrá\num\npá\nss\nar\no?", wrapped
+ when 3
+ assert_equal "Ser\ná\num\npás\nsar\no?", wrapped
+ when 4
+ assert_equal "Será\num\npáss\naro?", wrapped
+ when 5
+ assert_equal "Será\num\npássa\nro?", wrapped
+ when 6
+ assert_equal "Será\num\npássar\no?", wrapped
+ when 7
+ assert_equal "Será um\npássaro\n?", wrapped
+ when 15..8
+ assert_equal "Será um\npássaro?", wrapped
+ when 16..18
+ assert_equal "Será um pássaro?", wrapped
+ end
+ end
+ end
+end \ No newline at end of file