|
=begin |
|
Place in <hroot>/lib/ |
|
|
|
This module provides primitives for console user inputs. |
|
It asks for values until a condition is met. This can be used, for example, to |
|
ask for an email address until the user provides a valid address, e.g. |
|
|
|
``` |
|
email = ValidEmailAddress.new Text.new 'Email' |
|
address = email.to_s |
|
``` |
|
|
|
A convenience function is provided: |
|
|
|
``` |
|
password = ask_for "Password", Password, ComplicatedString |
|
password = password.to_s |
|
``` |
|
|
|
Objects are lazy, which means they only invoke their functionality |
|
when asking you ask them for their value with to_s. |
|
|
|
To create a new Prompt, inherit from Prompt, and override the .input-method. |
|
To create a requirement, inherit from Requirement, and implement the |
|
.met?-method. |
|
|
|
Author: Johannes C. Hofmeister, https://cessor.de, July 2018 |
|
License: I really don't care. Be excellent to each other. Not liable if you mess things up. |
|
Disclaimer at end of file. |
|
=end |
|
|
|
|
|
class Prompt |
|
# Base class for prompts. |
|
# Displays a message for the user and them for input from the console. |
|
protected |
|
|
|
def initialize(message) |
|
@message = message |
|
end |
|
|
|
def input |
|
raise NotImplementedError |
|
end |
|
|
|
def prompt_message |
|
@message.to_s + ": " |
|
end |
|
end |
|
|
|
|
|
class Text < Prompt |
|
# Asks the user for input from the console. |
|
# This is capable of blocking Rake tasks |
|
# A simple gets doesn't work. |
|
def input |
|
print prompt_message |
|
value = STDIN.gets |
|
value.chomp |
|
end |
|
end |
|
|
|
|
|
class Password < Prompt |
|
# Asks the user for input from the console. |
|
# Typed characters are hidden |
|
def input |
|
value = STDIN.getpass(prompt_message) |
|
value.chomp |
|
end |
|
end |
|
|
|
|
|
class Pair |
|
# A pair of two values which defaults to the first |
|
# Used by Confirmation to provide multiple return values |
|
def initialize(value1, value2) |
|
@value1 = value1 |
|
@value2 = value2 |
|
end |
|
|
|
def match? |
|
@value1.eql? @value2 |
|
end |
|
|
|
def to_s |
|
@value1 |
|
end |
|
end |
|
|
|
|
|
class Confirmation < Prompt |
|
# Prompts the user for values and returns them |
|
# them as a pair |
|
def initialize(message, prompt1, prompt2) |
|
@message = message |
|
@prompt1 = prompt1 |
|
@prompt2 = prompt2 |
|
end |
|
|
|
def input |
|
print @message |
|
Pair.new @prompt1.to_s, @prompt2.to_s |
|
end |
|
end |
|
|
|
|
|
class Requirement |
|
# Base class for requirements |
|
# A requirement will prompt for input until the requirement is met. |
|
|
|
def to_s |
|
response = '' |
|
while not met?(response) do |
|
response = @prompt.input |
|
end |
|
response.to_s |
|
end |
|
|
|
protected |
|
|
|
def initialize(prompt) |
|
@prompt = prompt |
|
end |
|
|
|
def met?(value) |
|
raise NotImplementedError |
|
end |
|
end |
|
|
|
|
|
class NotEmpty < Requirement |
|
# Providing documentation for this class seems unnecessary. |
|
# F***ing guess what it does. |
|
def met?(value) |
|
not value.to_s.empty? |
|
end |
|
end |
|
|
|
|
|
class ComplicatedString < Requirement |
|
# Make sure a value looks like some kind of password-like string |
|
# This does not really enforce strong passwords, but what can you do... |
|
# Ideally, this should enforce even stronger values. |
|
def met?(value) |
|
value.length >= 8 and value =~ /\d/ |
|
end |
|
end |
|
|
|
|
|
class ValidEmailAddress < Requirement |
|
# I did not actually test this Regex. I don't know. Give it something |
|
# with an @ in the middle, yes? |
|
EMAIL_PATTERN = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i |
|
def met?(value) |
|
value =~ EMAIL_PATTERN |
|
end |
|
end |
|
|
|
|
|
class ValuesMatch < Requirement |
|
# Used to make sure that the password and its confirmation match |
|
def met?(pair) |
|
not pair.is_a? String and pair.match? |
|
end |
|
end |
|
|
|
|
|
def ask_for(message, prompt, requirement) |
|
# Factory method for convenience & readability |
|
requirement.new prompt.new message |
|
end |
|
|
|
|
|
|
|
=begin |
|
Disclaimer |
|
========== |
|
|
|
I am a Python programmer by trade, and I am unfamiliar with Ruby coding |
|
conventions. I briefly skimmed https://github.com/rubocop-hq/ruby-style-guide. |
|
It might be that a library to get user input from the console already exists |
|
in Ruby, I didn't check as I wanted to learn some ruby. |
|
|
|
I am trying to write idiomatic Ruby code, but I default to Python idioms when |
|
in doubt. |
|
|
|
Prompt and Requirement are abstract base classes. I wanted to compose prompts |
|
and requirements, rather than inherit from a base class but I felt that Ruby |
|
semantic were too complicated. For example, Lambdas and Procs require to be |
|
invoked with .call, so I went with a proper object with a more meaningful |
|
method name instead (Requirement.met?). |
|
|
|
The reasoning for this OOP style is described in the book "Elegant Objects" by |
|
Yegor Bugayenko. The gist of it is to have many tiny, active objects, and to |
|
use objects as the central unit of expression. |
|
=end |