| 1 |
require 'tempfile' |
| 2 |
require "util" |
| 3 |
|
| 4 |
class Numeric |
| 5 |
def to_pretty_s |
| 6 |
%w(no one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen)[self] || to_s |
| 7 |
end |
| 8 |
end |
| 9 |
|
| 10 |
class NilClass |
| 11 |
def multiline prefix=nil; "" end |
| 12 |
end |
| 13 |
|
| 14 |
class String |
| 15 |
def ucfirst; self[0..0].upcase + self[1..-1] end |
| 16 |
def dcfirst; self[0..0].downcase + self[1..-1] end |
| 17 |
def blank?; self =~ /\A\s*\z/ end |
| 18 |
def underline; self + "\n" + ("-" * self.length) end |
| 19 |
def multiline prefix=""; blank? ? "" : "\n" + self.gsub(/^/, prefix) end |
| 20 |
def pluralize n; n.to_pretty_s + " " + (n == 1 ? self : self + "s") end |
| 21 |
def multistrip; strip.gsub(/\n\n+/, "\n\n") end |
| 22 |
end |
| 23 |
|
| 24 |
class Array |
| 25 |
def listify prefix="" |
| 26 |
return "" if empty? |
| 27 |
"\n" + |
| 28 |
map_with_index { |x, i| x.to_s.gsub(/^/, "#{prefix}#{i + 1}. ") }. |
| 29 |
join("\n") |
| 30 |
end |
| 31 |
end |
| 32 |
|
| 33 |
class Time |
| 34 |
def pretty; strftime "%c" end |
| 35 |
def pretty_date; strftime "%Y-%m-%d" end |
| 36 |
def ago |
| 37 |
diff = (Time.now - self).to_i.abs |
| 38 |
if diff < 60 |
| 39 |
"second".pluralize diff |
| 40 |
elsif diff < 60*60*3 |
| 41 |
"minute".pluralize(diff / 60) |
| 42 |
elsif diff < 60*60*24*3 |
| 43 |
"hour".pluralize(diff / (60*60)) |
| 44 |
elsif diff < 60*60*24*7*2 |
| 45 |
"day".pluralize(diff / (60*60*24)) |
| 46 |
elsif diff < 60*60*24*7*8 |
| 47 |
"week".pluralize(diff / (60*60*24*7)) |
| 48 |
elsif diff < 60*60*24*7*52 |
| 49 |
"month".pluralize(diff / (60*60*24*7*4)) |
| 50 |
else |
| 51 |
"year".pluralize(diff / (60*60*24*7*52)) |
| 52 |
end |
| 53 |
end |
| 54 |
end |
| 55 |
|
| 56 |
module Lowline |
| 57 |
def run_editor |
| 58 |
f = Tempfile.new "ditz" |
| 59 |
yield f |
| 60 |
f.close |
| 61 |
|
| 62 |
editor = ENV["EDITOR"] || "/usr/bin/vi" |
| 63 |
cmd = "#{editor} #{f.path.inspect}" |
| 64 |
|
| 65 |
mtime = File.mtime f.path |
| 66 |
system cmd or raise Error, "cannot execute command: #{cmd.inspect}" |
| 67 |
|
| 68 |
File.mtime(f.path) == mtime ? nil : f.path |
| 69 |
end |
| 70 |
|
| 71 |
def ask q, opts={} |
| 72 |
default_s = case opts[:default] |
| 73 |
when nil; nil |
| 74 |
when ""; " (enter for none)" |
| 75 |
else; " (enter for #{opts[:default].inspect})" |
| 76 |
end |
| 77 |
|
| 78 |
tail = case q |
| 79 |
when /[:?]$/; " " |
| 80 |
when /[:?]\s+$/; "" |
| 81 |
else; ": " |
| 82 |
end |
| 83 |
|
| 84 |
while true |
| 85 |
prompt = [q, default_s, tail].compact.join |
| 86 |
if Ditz::has_readline? |
| 87 |
ans = Readline::readline(prompt) |
| 88 |
else |
| 89 |
print prompt |
| 90 |
ans = gets.strip |
| 91 |
end |
| 92 |
if opts[:default] |
| 93 |
ans = opts[:default] if ans.blank? |
| 94 |
else |
| 95 |
next if ans.blank? && !opts[:empty_ok] |
| 96 |
end |
| 97 |
break ans unless (opts[:restrict] && ans !~ opts[:restrict]) |
| 98 |
end |
| 99 |
end |
| 100 |
|
| 101 |
def ask_via_editor q, default=nil |
| 102 |
fn = run_editor do |f| |
| 103 |
f.puts q.gsub(/^/, "## ") |
| 104 |
f.puts "##" |
| 105 |
f.puts "## Enter your text below. Lines starting with a '#' will be ignored." |
| 106 |
f.puts |
| 107 |
f.puts default if default |
| 108 |
end |
| 109 |
return unless fn |
| 110 |
IO.read(fn).gsub(/^#.*$/, "").multistrip |
| 111 |
end |
| 112 |
|
| 113 |
def ask_multiline q |
| 114 |
puts "#{q} (ctrl-d, ., or /stop to stop, /edit to edit, /reset to reset):" |
| 115 |
ans = "" |
| 116 |
while true |
| 117 |
if Ditz::has_readline? |
| 118 |
line = Readline::readline('> ') |
| 119 |
else |
| 120 |
(line = gets) && line.strip! |
| 121 |
end |
| 122 |
if line |
| 123 |
if Ditz::has_readline? |
| 124 |
Readline::HISTORY.push(line) |
| 125 |
end |
| 126 |
case line |
| 127 |
when /^\.$/, "/stop" |
| 128 |
break |
| 129 |
when "/reset" |
| 130 |
return ask_multiline(q) |
| 131 |
when "/edit" |
| 132 |
return ask_via_editor(q, ans) |
| 133 |
else |
| 134 |
ans << line + "\n" |
| 135 |
end |
| 136 |
else |
| 137 |
puts |
| 138 |
break |
| 139 |
end |
| 140 |
end |
| 141 |
ans.multistrip |
| 142 |
end |
| 143 |
|
| 144 |
def ask_yon q |
| 145 |
while true |
| 146 |
print "#{q} (y/n): " |
| 147 |
a = gets.strip |
| 148 |
break a if a =~ /^[yn]$/i |
| 149 |
end =~ /y/i |
| 150 |
end |
| 151 |
|
| 152 |
def ask_for_many plural_name, name=nil |
| 153 |
name ||= plural_name.gsub(/s$/, "") |
| 154 |
stuff = [] |
| 155 |
|
| 156 |
while true |
| 157 |
puts |
| 158 |
puts "Current #{plural_name}:" |
| 159 |
if stuff.empty? |
| 160 |
puts "None!" |
| 161 |
else |
| 162 |
stuff.each_with_index { |c, i| puts " #{i + 1}) #{c}" } |
| 163 |
end |
| 164 |
puts |
| 165 |
ans = ask "(A)dd #{name}, (r)emove #{name}, or (d)one" |
| 166 |
case ans |
| 167 |
when "a", "A" |
| 168 |
ans = ask "#{name.ucfirst} name", "" |
| 169 |
stuff << ans unless ans =~ /^\s*$/ |
| 170 |
when "r", "R" |
| 171 |
ans = ask "Remove which component? (1--#{stuff.size})" |
| 172 |
stuff.delete_at(ans.to_i - 1) if ans |
| 173 |
when "d", "D" |
| 174 |
break |
| 175 |
end |
| 176 |
end |
| 177 |
stuff |
| 178 |
end |
| 179 |
|
| 180 |
def ask_for_selection stuff, name, to_string=:to_s |
| 181 |
puts "Choose a #{name}:" |
| 182 |
stuff.each_with_index do |c, i| |
| 183 |
pretty = case to_string |
| 184 |
when block_given? && to_string |
| 185 |
yield c |
| 186 |
when Symbol |
| 187 |
c.send to_string |
| 188 |
when Proc |
| 189 |
to_string.call c |
| 190 |
else |
| 191 |
raise ArgumentError, "unknown to_string argument type; expecting Proc or Symbol" |
| 192 |
end |
| 193 |
puts " #{i + 1}) #{pretty}" |
| 194 |
end |
| 195 |
|
| 196 |
j = while true |
| 197 |
i = ask "#{name.ucfirst} (1--#{stuff.size})" |
| 198 |
break i.to_i if i && (1 .. stuff.size).member?(i.to_i) |
| 199 |
end |
| 200 |
|
| 201 |
stuff[j - 1] |
| 202 |
end |
| 203 |
end |