Fix up time display format
[bus.git] / maxious-canberra-transit-feed / highline.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
module HighLine
  # prompt = text to display
  # type can be one of :string, :integer, :float, :bool or a proc
  #   if it's a proc then it is called with the entered string. If the input
  #     cannot be converted then it should throw an exception
  #   if type == :bool then y,yes are converted to true. n,no are converted to
  #     false. All other values are rejected.
  #
  # options should be a hash of validation options
  #   :validate => regular expresion or proc
  #     if validate is a regular expression then the input is matched against it
  #     if it's a proc then the proc is called and the input is accepted if it
  #       returns true
  #   :between => range
  #     the input is checked if it lies within the range
  #   :above => value
  #     the input is checked if it is above the value
  #   :below => value
  #     the input is checked if it is less than the value
  #   :default => string
  #     if the user doesn't enter a value then the default value is returned
  #   :base => [b, o, d, x]
  #     when asking for integers this will take a number in binary, octal,
  #     decimal or hexadecimal
  def ask(prompt, type, options=nil)
    begin
      valid = true
 
      default = option(options, :default)
      if default
        defaultstr = " |#{default}|"
      else
        defaultstr = ""
      end
 
      base = option(options, :base)
 
      print prompt, "#{defaultstr} "
      $stdout.flush
      input = gets.chomp
 
      if default && input == ""
        input = default
      end
 
      #comvert the input to the correct type
      input = case type
              when :string: input
              when :integer: convert(input, base) rescue valid = false
              when :float: Float(input) rescue valid = false
              when :bool
                valid = input =~ /^(y|n|yes|no)$/
                input[0] == ?y
              when Proc: input = type.call(input) rescue valid = false
              end
 
      #validate the input
      valid &&= validate(options, :validate) do |test|
        case test
        when Regexp: input =~ test
        when Proc: test.call(input)
        end
      end
      valid &&= validate(options, :within) { |range| range === input}
      valid &&= validate(options, :above) { |value| input > value}
      valid &&= validate(options, :below) { |value| input < value}
 
      puts "Not a valid value" unless valid
    end until valid
 
    return input
  end
 
  #asks a yes/no question
  def ask_if(prompt)
    ask(prompt, :bool)
  end
 
  private
 
  #extracts a key from the options hash
  def option(options, key)
    result = nil
    if options && options.key?(key)
      result = options[key]
    end
    result
  end
 
  #helper function for validation
  def validate(options, key)
    result = true
    if options && options.key?(key)
      result = yield options[key]
    end
    result    
  end
 
  #converts a string to an integer
  #input = the value to convert
  #base = the numeric base of the value b,o,d,x
  def convert(input, base)
    if base
      if ["b", "o", "d", "x"].include?(base)
        input = "0#{base}#{input}"
        value = Integer(input)
      else
        value = Integer(input)
      end
    else
      value = Integer(input)
    end
 
    value
  end
end
 
 
 
if __FILE__ == $0
  include HighLine
  #string input using a regexp to validate, returns test as the default value
  p ask("enter a string, (all lower case)", :string, :validate => /^[a-z]*$/, :default => "test")
  #string input using a proc to validate
  p ask("enter a string, (between 3 and 6 characters)", :string, :validate => proc { |input| (3..6) === input.length})
 
  #integer intput using :within
  p ask("enter an integer, (0-10)", :integer, :within => 0..10)
  #float input using :above
  p ask("enter a float, (> 6)", :float, :above => 6)
 
  #getting a binary value
  p ask("enter a binary number", :integer, :base => "b")
 
  #using a proc to convert the a comma seperated list into an array
  p ask("enter a comma seperated list", proc { |x| x.split(/,/)})
 
  p ask_if("do you want to continue?")
end