Blob of lib/lowline.rb (raw blob data)

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 # oh yeah
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 # heh
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