annoy 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. data/CHANGES.txt +7 -0
  2. data/LICENSE.txt +19 -0
  3. data/README.rdoc +23 -0
  4. data/Rakefile +104 -0
  5. data/annoy.gemspec +56 -0
  6. data/lib/annoy.rb +297 -0
  7. metadata +84 -0
@@ -0,0 +1,7 @@
1
+ ANNOY, CHANGES
2
+
3
+
4
+ #### 0.5 (2009-05-07) ###############################
5
+
6
+ * First public release. See commit history for solutious-rudy.
7
+
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2009 Delano Mandelbaum, Solutious Inc
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,23 @@
1
+ = Annoy - v0.5
2
+
3
+ All your system-independent infoz in one handy class.
4
+
5
+ == Installation
6
+
7
+ Via Rubygems, one of:
8
+
9
+ $ sudo gem install annoy
10
+ $ sudo gem install delano-annoy --source http://gems.github.com/
11
+
12
+ or via download:
13
+ * annoy-latest.tar.gz[http://github.com/delano/annoy/tarball/latest]
14
+ * annoy-latest.zip[http://github.com/delano/annoy/zipball/latest]
15
+
16
+
17
+ == Credits
18
+
19
+ * Delano Mandelbaum (delano@solutious.com)
20
+
21
+ == License
22
+
23
+ See: LICENSE.txt
@@ -0,0 +1,104 @@
1
+ require 'rubygems'
2
+ require 'rake/clean'
3
+ require 'rake/gempackagetask'
4
+ require 'hanna/rdoctask'
5
+ require 'fileutils'
6
+ include FileUtils
7
+
8
+ task :default => :package
9
+
10
+ # CONFIG =============================================================
11
+
12
+ # Change the following according to your needs
13
+ README = "README.rdoc"
14
+ CHANGES = "CHANGES.txt"
15
+ LICENSE = "LICENSE.txt"
16
+
17
+ # Files and directories to be deleted when you run "rake clean"
18
+ CLEAN.include [ 'pkg', '*.gem', '.config']
19
+
20
+ # Virginia assumes your project and gemspec have the same name
21
+ name = (Dir.glob('*.gemspec') || ['virginia']).first.split('.').first
22
+ load "#{name}.gemspec"
23
+ version = @spec.version
24
+
25
+ # That's it! The following defaults should allow you to get started
26
+ # on other things.
27
+
28
+
29
+ # TESTS/SPECS =========================================================
30
+
31
+
32
+
33
+ # INSTALL =============================================================
34
+
35
+ Rake::GemPackageTask.new(@spec) do |p|
36
+ p.need_tar = true if RUBY_PLATFORM !~ /mswin/
37
+ end
38
+
39
+ task :release => [ :rdoc, :package ]
40
+ task :install => [ :rdoc, :package ] do
41
+ sh %{sudo gem install pkg/#{name}-#{version}.gem}
42
+ end
43
+ task :uninstall => [ :clean ] do
44
+ sh %{sudo gem uninstall #{name}}
45
+ end
46
+
47
+
48
+ # RUBYFORGE RELEASE / PUBLISH TASKS ==================================
49
+
50
+ if @spec.rubyforge_project
51
+ desc 'Publish website to rubyforge'
52
+ task 'publish:rdoc' => 'doc/index.html' do
53
+ sh "scp -rp doc/* rubyforge.org:/var/www/gforge-projects/#{name}/"
54
+ end
55
+
56
+ desc 'Public release to rubyforge'
57
+ task 'publish:gem' => [:package] do |t|
58
+ sh <<-end
59
+ rubyforge add_release -o Any -a #{CHANGES} -f -n #{README} #{name} #{name} #{@spec.version} pkg/#{name}-#{@spec.version}.gem &&
60
+ rubyforge add_file -o Any -a #{CHANGES} -f -n #{README} #{name} #{name} #{@spec.version} pkg/#{name}-#{@spec.version}.tgz
61
+ end
62
+ end
63
+ end
64
+
65
+
66
+
67
+ # RUBY DOCS TASK ==================================
68
+
69
+ Rake::RDocTask.new do |t|
70
+ t.rdoc_dir = 'doc'
71
+ t.title = @spec.summary
72
+ t.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
73
+ t.options << '--charset' << 'utf-8'
74
+ t.rdoc_files.include(LICENSE)
75
+ t.rdoc_files.include(README)
76
+ t.rdoc_files.include(CHANGES)
77
+ #t.rdoc_files.include('bin/*')
78
+ t.rdoc_files.include('lib/**/*.rb')
79
+ end
80
+
81
+
82
+
83
+
84
+ #Hoe.new('rspec', Spec::VERSION::STRING) do |p|
85
+ # p.summary = Spec::VERSION::SUMMARY
86
+ # p.description = "Behaviour Driven Development for Ruby."
87
+ # p.rubyforge_name = 'rspec'
88
+ # p.developer('RSpec Development Team', 'rspec-devel@rubyforge.org')
89
+ # p.extra_dev_deps = [["cucumber",">= 0.1.13"]]
90
+ # p.remote_rdoc_dir = "rspec/#{Spec::VERSION::STRING}"
91
+ # p.rspec_options = ['--options', 'spec/spec.opts']
92
+ # p.history_file = 'History.rdoc'
93
+ # p.readme_file = 'README.rdoc'
94
+ # p.post_install_message = <<-POST_INSTALL_MESSAGE
95
+ ##{'*'*50}
96
+ #
97
+ # Thank you for installing rspec-#{Spec::VERSION::STRING}
98
+ #
99
+ # Please be sure to read History.rdoc and Upgrade.rdoc
100
+ # for useful information about this release.
101
+ #
102
+ #{'*'*50}
103
+ #POST_INSTALL_MESSAGE
104
+ #end
@@ -0,0 +1,56 @@
1
+ @spec = Gem::Specification.new do |s|
2
+ s.name = "annoy"
3
+ s.rubyforge_project = "annoy"
4
+ s.version = "0.5.0"
5
+ s.summary = "Annoy: Like your annoying friend that asks you questions all the time."
6
+ s.description = s.summary
7
+ s.author = "Delano Mandelbaum"
8
+ s.email = "delano@solutious.com"
9
+ s.homepage = "http://solutious.com/"
10
+
11
+
12
+ # = EXECUTABLES =
13
+ # The list of executables in your project (if any). Don't include the path,
14
+ # just the base filename.
15
+ s.executables = %w[]
16
+
17
+ # = DEPENDENCIES =
18
+ # Add all gem dependencies
19
+ #s.add_dependency ''
20
+ s.add_dependency 'highline', '>= 1.5.0'
21
+
22
+ # = MANIFEST =
23
+ # The complete list of files to be included in the release. When GitHub packages your gem,
24
+ # it doesn't allow you to run any command that accesses the filesystem. You will get an
25
+ # error. You can ask your VCS for the list of versioned files:
26
+ # git ls-files
27
+ # svn list -R
28
+ s.files = %w(
29
+ CHANGES.txt
30
+ LICENSE.txt
31
+ README.rdoc
32
+ Rakefile
33
+ lib/annoy.rb
34
+ annoy.gemspec
35
+ )
36
+
37
+ s.extra_rdoc_files = %w[README.rdoc LICENSE.txt]
38
+ s.has_rdoc = true
39
+ s.rdoc_options = ["--line-numbers", "--title", s.summary, "--main", "README.rdoc"]
40
+ s.require_paths = %w[lib]
41
+ s.rubygems_version = '1.3.0'
42
+
43
+ if s.respond_to? :specification_version then
44
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
45
+ s.specification_version = 2
46
+
47
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
48
+ s.add_runtime_dependency(%q<RedCloth>, [">= 4.0.4"])
49
+ else
50
+ s.add_dependency(%q<RedCloth>, [">= 4.0.4"])
51
+ end
52
+ else
53
+ s.add_dependency(%q<RedCloth>, [">= 4.0.4"])
54
+ end
55
+
56
+ end
@@ -0,0 +1,297 @@
1
+ #---
2
+ # TODO: Use Matrix to give a more accurate annoyance factor
3
+ # TODO: Add trivia questions
4
+ #+++
5
+
6
+ require 'timeout'
7
+ require 'highline'
8
+
9
+ # = Annoy
10
+ #
11
+ # Like your annoying friend that asks you questions all the time.
12
+ #
13
+ # Rudy uses Annoy to present the user with a simple question before
14
+ # continuing with a destructive action.
15
+ #
16
+ class Annoy
17
+
18
+ attr_accessor :factor
19
+ attr_accessor :flavor
20
+ attr_accessor :answer
21
+ attr_accessor :writer
22
+ attr_accessor :period
23
+ attr_accessor :system
24
+
25
+ @@operators = {
26
+ :low => %w(+ -),
27
+ :medium => %w(* -),
28
+ :high => %w(& * -),
29
+ :insane => %w(** << | & *)
30
+ }.freeze
31
+
32
+ @@strlen = {
33
+ :low => 2,
34
+ :medium => 3,
35
+ :high => 4,
36
+ :insane => 32
37
+ }.freeze
38
+
39
+ @@randsize = {
40
+ :low => 10,
41
+ :medium => 12,
42
+ :high => 50,
43
+ :insane => 1000
44
+ }.freeze
45
+
46
+ @@period = 60.freeze # max seconds to wait
47
+ @@flavors = [:numeric, :string].freeze
48
+ @@skip = false # skip questions
49
+
50
+ # Calling this method tells Annoy to not prompt for
51
+ # a response. All questions will return true.
52
+ def Annoy.enable_skip; @@skip = true; end
53
+ # Tells annoy to prompt for a response.
54
+ def Annoy.disable_skip; @@skip = false; end
55
+ # Returns true of Annoy is in skip mode
56
+ def Annoy.skip?; @@skip; end
57
+
58
+ # * +factor+ annoyance factor, one of :low (default), :medium, :high, :insane
59
+ # * +flavor+ annoyance flavor, one of :rand (default), :numeric, string
60
+ # * +writer+ an IO object to write to. Default: STDERR
61
+ # * +period+ the amount of time to wait in seconds. Default: 60
62
+ def initialize(opts={:factor=>:medium, :flavor=>:rand, :writer=>STDOUT, :period=>nil})
63
+ @factor = opts[:factor]
64
+ @flavor = Annoy.get_flavor(opts[:flavor])
65
+ @writer = opts[:writer]
66
+ @period = opts[:period] || @@period
67
+ unless Annoy.respond_to?("#{@flavor}_question")
68
+ raise "Hey, hey, hey. I don't know that flavor! (#{@flavor})"
69
+ end
70
+ end
71
+
72
+ # Generates and returns a question. The correct response is available
73
+ # as +@answer+.
74
+ def question
75
+ q, @answer =Annoy.question(@factor, @flavor)
76
+ q
77
+ end
78
+
79
+ # A wrapper for string_question and numberic_question
80
+ def Annoy.question(factor=:medium, flavor=:rand)
81
+ raise "Come on, you ruined the flavor!" unless flavor
82
+ Annoy.send("#{flavor}_question", factor)
83
+ end
84
+
85
+ # Generates a random string
86
+ def Annoy.string_question(factor=:medium)
87
+ # Strings don't need to be evaluated so the answer is the
88
+ # same as the question.
89
+ str = strand @@strlen[factor]
90
+ [str,str]
91
+ end
92
+
93
+ # * Generates a rudimentary numeric equation in the form: (Integer OPERATOR Integer).
94
+ # * Returns [equation, answer]
95
+ def Annoy.numeric_question(factor=:medium)
96
+ equation = answer = 0
97
+ while answer < 10
98
+ vals = [rand(@@randsize[factor])+1,
99
+ @@operators[factor][ rand(@@operators[factor].size) ],
100
+ rand(@@randsize[factor])+1 ]
101
+ equation = "(%d %s %d)" % vals
102
+ answer = eval(equation)
103
+ end
104
+ [equation, answer]
105
+ end
106
+
107
+ # Prints a question to +writer+ and waits for a response on STDIN.
108
+ # It checks whether STDIN is connected a tty so it doesn't block on gets
109
+ # when there's no human around to annoy. It will return <b>TRUE</b> when
110
+ # STDIN is NOT connected to a tty (when STDIN.tty? returns false).
111
+ # * +msg+ The message to print. Default: "Please confirm."
112
+ # Returns true when the answer is correct, otherwise false.
113
+ def Annoy.challenge?(msg="Please confirm.", factor=:medium, flavor=:rand, writer=STDOUT, period=nil)
114
+ return true unless STDIN.tty? # Humans only!
115
+ return true if Annoy.skip?
116
+ begin
117
+ success = Timeout::timeout(period || @@period) do
118
+ flavor = Annoy.get_flavor(flavor)
119
+ question, answer = Annoy.question(factor, flavor)
120
+ msg = "#{msg} To continue, #{Annoy.verb(flavor)} #{question}: "
121
+ #writer.print msg
122
+ #if ![:medium, :high, :insane].member?(factor) && flavor == :numeric
123
+ #writer.print "(#{answer}) "
124
+ #writer.flush
125
+ #end
126
+ #response = Annoy.get_response(writer)
127
+
128
+ highline = HighLine.new
129
+ response = highline.ask(msg) { |q|
130
+ q.echo = '*' # Don't display response
131
+ q.overwrite = true # Erase the question afterwards
132
+ q.whitespace = :strip # Remove whitespace from the response
133
+ q.answer_type = Integer if flavor == :numeric
134
+ }
135
+
136
+ ret = (response == answer)
137
+ writer.puts "Incorrect" unless ret
138
+ ret
139
+ end
140
+ rescue Interrupt
141
+ writer.puts $/, "Giving up!"
142
+ false
143
+ rescue Annoy::GiveUp => ex
144
+ writer.puts $/, "Giving up!"
145
+ false
146
+ rescue Timeout::Error => ex
147
+ writer.puts $/, "Times up!"
148
+ false
149
+ end
150
+ end
151
+
152
+ # Runs a challenge with the message, "Are you sure?"
153
+ # See: Annoy.challenge?
154
+ def Annoy.are_you_sure?(factor=:medium, flavor=:rand, writer=STDOUT)
155
+ Annoy.challenge?("Are you sure?", factor, flavor, writer)
156
+ end
157
+
158
+ # Runs a challenge with the message, "Proceed?"
159
+ # See: Annoy.challenge?
160
+ def Annoy.proceed?(factor=:medium, flavor=:rand, writer=STDOUT)
161
+ Annoy.challenge?("Proceed?", factor, flavor, writer)
162
+ end
163
+
164
+ # See: Annoy.challenge?
165
+ # Uses the value of @flavor, @factor, and @writer
166
+ def challenge?(msg="Please confirm.")
167
+ Annoy.challenge?(msg, @factor, @flavor, @writer)
168
+ end
169
+
170
+ # See: Annoy.pose_question
171
+ # Uses the value of @writer
172
+ def pose_question(msg, regexp)
173
+ Annoy.pose_question(msg, regexp, @writer)
174
+ end
175
+
176
+ # Prints a question to writer and waits for a response on STDIN.
177
+ # It checks whether STDIN is connected a tty so it doesn't block on gets.
178
+ # when there's no human around to annoy. It will return <b>TRUE</b> when
179
+ # STDIN is NOT connected to a tty.
180
+ # * +msg+ The question to pose to the user
181
+ # * +regexp+ The regular expression to match the answer.
182
+ def Annoy.pose_question(msg, regexp, writer=STDOUT, period=nil)
183
+ return true unless STDIN.tty? # Only ask a question if there's a human
184
+ return true if Annoy.skip?
185
+ begin
186
+ success = Timeout::timeout(period || @@period) do
187
+ regexp &&= Regexp.new regexp
188
+ highline = HighLine.new
189
+ response = highline.ask(msg) { |q|
190
+ q.echo = '*' # Don't display response
191
+ q.overwrite = true # Erase the question afterwards
192
+ q.whitespace = :strip # Remove whitespace from the response
193
+ }
194
+ regexp.match(response)
195
+ end
196
+ rescue Timeout::Error => ex
197
+ writer.puts $/, "Times up!"
198
+ false
199
+ end
200
+ end
201
+
202
+
203
+ def Annoy.timed_display(msg, writer, period=nil)
204
+ return true unless STDIN.tty? # Only ask a question if there's a human
205
+ if Annoy.skip?
206
+ #writer.puts msg
207
+ return true
208
+ end
209
+ begin
210
+ period ||= @@period
211
+ success = Timeout::timeout(period) do
212
+ writer.puts "Message will display for #{period} seconds"
213
+ writer.print msg
214
+ writer.flush if writer.respond_to?(:flush)
215
+ sleep period+1
216
+ end
217
+ rescue Timeout::Error => ex
218
+ writer.print "\r" << ' '*msg.size
219
+ end
220
+
221
+ true
222
+ end
223
+
224
+ private
225
+ def Annoy.get_response(writer=STDOUT)
226
+ return true unless STDIN.tty? # Humans only
227
+ return true if Annoy.skip?
228
+ # TODO: Count the number of keystrokes to prevent copy/paste.
229
+ # We can probably use Highline.
230
+ # We likely need to be more specific but this will do for now.
231
+ #if ::SysInfo.new.os == :unix
232
+ # begin
233
+ # response = []
234
+ # char = nil
235
+ # system("stty raw -echo") # Raw mode, no echo
236
+ # while char != "\r" || response.size > 5
237
+ # char = STDIN.getc.chr
238
+ # writer.print char
239
+ # writer.flush
240
+ # response << char
241
+ # end
242
+ # writer.print "\n\r"
243
+ # response = response.join('')
244
+ # rescue => ex
245
+ # ensure
246
+ # system("stty -raw echo") # Reset terminal mode
247
+ # end
248
+ #else
249
+ response = (STDIN.gets || "")
250
+ #end
251
+ response.chomp.gsub(/["']/, '')
252
+ end
253
+ # Returns a verb appropriate to the flavor.
254
+ # * :numeric => resolve
255
+ # * :string => type
256
+ def Annoy.verb(flavor)
257
+ case flavor
258
+ when :numeric then "resolve"
259
+ when :string then "type"
260
+ else
261
+ nil
262
+ end
263
+ end
264
+
265
+ #
266
+ # Generates a string of random alphanumeric characters.
267
+ # * +len+ is the length, an Integer. Default: 8
268
+ # * +safe+ in safe-mode, ambiguous characters are removed (default: true):
269
+ # i l o 1 0
270
+ def Annoy.strand( len=8, safe=true )
271
+ chars = ("a".."z").to_a + ("0".."9").to_a
272
+ chars.delete_if { |v| %w(i l o 1 0).member?(v) } if safe
273
+ str = ""
274
+ 1.upto(len) { |i| str << chars[rand(chars.size-1)] }
275
+ str
276
+ end
277
+
278
+ # * +f+ a prospective flavor name
279
+ def Annoy.get_flavor(f)
280
+ f.to_sym == :rand ? flavor_rand : f.to_sym
281
+ end
282
+
283
+ # Return a random flavor
284
+ def Annoy.flavor_rand
285
+ @@flavors[rand(@@flavors.size)]
286
+ end
287
+
288
+
289
+ end
290
+
291
+ # = Annoy::GiveUp
292
+ #
293
+ # This is what happens when you don't answer Annoy's questions.
294
+ class Annoy::GiveUp < RuntimeError
295
+ end
296
+
297
+
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: annoy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ platform: ruby
6
+ authors:
7
+ - Delano Mandelbaum
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-05-07 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: highline
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.5.0
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: RedCloth
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 4.0.4
34
+ version:
35
+ description: "Annoy: Like your annoying friend that asks you questions all the time."
36
+ email: delano@solutious.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.rdoc
43
+ - LICENSE.txt
44
+ files:
45
+ - CHANGES.txt
46
+ - LICENSE.txt
47
+ - README.rdoc
48
+ - Rakefile
49
+ - lib/annoy.rb
50
+ - annoy.gemspec
51
+ has_rdoc: true
52
+ homepage: http://solutious.com/
53
+ licenses: []
54
+
55
+ post_install_message:
56
+ rdoc_options:
57
+ - --line-numbers
58
+ - --title
59
+ - "Annoy: Like your annoying friend that asks you questions all the time."
60
+ - --main
61
+ - README.rdoc
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: "0"
75
+ version:
76
+ requirements: []
77
+
78
+ rubyforge_project: annoy
79
+ rubygems_version: 1.3.2
80
+ signing_key:
81
+ specification_version: 2
82
+ summary: "Annoy: Like your annoying friend that asks you questions all the time."
83
+ test_files: []
84
+