monetra-ruby 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. data/Rakefile +35 -0
  2. data/lib/monetra.rb +239 -0
  3. data/lib/monetra/active_support.rb +42 -0
  4. data/lib/monetra/active_support/binding_of_caller.rb +84 -0
  5. data/lib/monetra/active_support/breakpoint.rb +523 -0
  6. data/lib/monetra/active_support/caching_tools.rb +62 -0
  7. data/lib/monetra/active_support/clean_logger.rb +38 -0
  8. data/lib/monetra/active_support/core_ext.rb +1 -0
  9. data/lib/monetra/active_support/core_ext/array.rb +7 -0
  10. data/lib/monetra/active_support/core_ext/array/conversions.rb +72 -0
  11. data/lib/monetra/active_support/core_ext/array/grouping.rb +46 -0
  12. data/lib/monetra/active_support/core_ext/bigdecimal.rb +3 -0
  13. data/lib/monetra/active_support/core_ext/bigdecimal/formatting.rb +7 -0
  14. data/lib/monetra/active_support/core_ext/blank.rb +50 -0
  15. data/lib/monetra/active_support/core_ext/cgi.rb +5 -0
  16. data/lib/monetra/active_support/core_ext/cgi/escape_skipping_slashes.rb +14 -0
  17. data/lib/monetra/active_support/core_ext/class.rb +3 -0
  18. data/lib/monetra/active_support/core_ext/class/attribute_accessors.rb +44 -0
  19. data/lib/monetra/active_support/core_ext/class/inheritable_attributes.rb +115 -0
  20. data/lib/monetra/active_support/core_ext/class/removal.rb +24 -0
  21. data/lib/monetra/active_support/core_ext/date.rb +6 -0
  22. data/lib/monetra/active_support/core_ext/date/conversions.rb +39 -0
  23. data/lib/monetra/active_support/core_ext/enumerable.rb +62 -0
  24. data/lib/monetra/active_support/core_ext/exception.rb +33 -0
  25. data/lib/monetra/active_support/core_ext/hash.rb +13 -0
  26. data/lib/monetra/active_support/core_ext/hash/conversions.rb +148 -0
  27. data/lib/monetra/active_support/core_ext/hash/diff.rb +11 -0
  28. data/lib/monetra/active_support/core_ext/hash/indifferent_access.rb +88 -0
  29. data/lib/monetra/active_support/core_ext/hash/keys.rb +53 -0
  30. data/lib/monetra/active_support/core_ext/hash/reverse_merge.rb +25 -0
  31. data/lib/monetra/active_support/core_ext/integer.rb +7 -0
  32. data/lib/monetra/active_support/core_ext/integer/even_odd.rb +24 -0
  33. data/lib/monetra/active_support/core_ext/integer/inflections.rb +15 -0
  34. data/lib/monetra/active_support/core_ext/kernel.rb +4 -0
  35. data/lib/monetra/active_support/core_ext/kernel/agnostics.rb +11 -0
  36. data/lib/monetra/active_support/core_ext/kernel/daemonizing.rb +15 -0
  37. data/lib/monetra/active_support/core_ext/kernel/reporting.rb +51 -0
  38. data/lib/monetra/active_support/core_ext/kernel/requires.rb +24 -0
  39. data/lib/monetra/active_support/core_ext/load_error.rb +38 -0
  40. data/lib/monetra/active_support/core_ext/logger.rb +16 -0
  41. data/lib/monetra/active_support/core_ext/module.rb +7 -0
  42. data/lib/monetra/active_support/core_ext/module/aliasing.rb +57 -0
  43. data/lib/monetra/active_support/core_ext/module/attr_internal.rb +31 -0
  44. data/lib/monetra/active_support/core_ext/module/attribute_accessors.rb +44 -0
  45. data/lib/monetra/active_support/core_ext/module/delegation.rb +41 -0
  46. data/lib/monetra/active_support/core_ext/module/inclusion.rb +11 -0
  47. data/lib/monetra/active_support/core_ext/module/introspection.rb +21 -0
  48. data/lib/monetra/active_support/core_ext/module/loading.rb +13 -0
  49. data/lib/monetra/active_support/core_ext/name_error.rb +20 -0
  50. data/lib/monetra/active_support/core_ext/numeric.rb +7 -0
  51. data/lib/monetra/active_support/core_ext/numeric/bytes.rb +44 -0
  52. data/lib/monetra/active_support/core_ext/numeric/time.rb +72 -0
  53. data/lib/monetra/active_support/core_ext/object.rb +2 -0
  54. data/lib/monetra/active_support/core_ext/object/extending.rb +47 -0
  55. data/lib/monetra/active_support/core_ext/object/misc.rb +34 -0
  56. data/lib/monetra/active_support/core_ext/pathname.rb +7 -0
  57. data/lib/monetra/active_support/core_ext/pathname/clean_within.rb +14 -0
  58. data/lib/monetra/active_support/core_ext/proc.rb +12 -0
  59. data/lib/monetra/active_support/core_ext/range.rb +5 -0
  60. data/lib/monetra/active_support/core_ext/range/conversions.rb +21 -0
  61. data/lib/monetra/active_support/core_ext/string.rb +13 -0
  62. data/lib/monetra/active_support/core_ext/string/access.rb +58 -0
  63. data/lib/monetra/active_support/core_ext/string/conversions.rb +19 -0
  64. data/lib/monetra/active_support/core_ext/string/inflections.rb +153 -0
  65. data/lib/monetra/active_support/core_ext/string/iterators.rb +17 -0
  66. data/lib/monetra/active_support/core_ext/string/starts_ends_with.rb +20 -0
  67. data/lib/monetra/active_support/core_ext/symbol.rb +12 -0
  68. data/lib/monetra/active_support/core_ext/time.rb +7 -0
  69. data/lib/monetra/active_support/core_ext/time/calculations.rb +188 -0
  70. data/lib/monetra/active_support/core_ext/time/conversions.rb +36 -0
  71. data/lib/monetra/active_support/dependencies.rb +187 -0
  72. data/lib/monetra/active_support/deprecation.rb +106 -0
  73. data/lib/monetra/active_support/inflections.rb +53 -0
  74. data/lib/monetra/active_support/inflector.rb +179 -0
  75. data/lib/monetra/active_support/json.rb +37 -0
  76. data/lib/monetra/active_support/json/encoders.rb +25 -0
  77. data/lib/monetra/active_support/json/encoders/core.rb +65 -0
  78. data/lib/monetra/active_support/option_merger.rb +25 -0
  79. data/lib/monetra/active_support/ordered_options.rb +50 -0
  80. data/lib/monetra/active_support/reloadable.rb +30 -0
  81. data/lib/monetra/active_support/values/time_zone.rb +180 -0
  82. data/lib/monetra/active_support/vendor/builder.rb +13 -0
  83. data/lib/monetra/active_support/vendor/builder/blankslate.rb +63 -0
  84. data/lib/monetra/active_support/vendor/builder/xchar.rb +112 -0
  85. data/lib/monetra/active_support/vendor/builder/xmlbase.rb +145 -0
  86. data/lib/monetra/active_support/vendor/builder/xmlevents.rb +63 -0
  87. data/lib/monetra/active_support/vendor/builder/xmlmarkup.rb +328 -0
  88. data/lib/monetra/active_support/vendor/flexmock.rb +84 -0
  89. data/lib/monetra/active_support/vendor/xml_simple.rb +1019 -0
  90. data/lib/monetra/active_support/version.rb +9 -0
  91. data/lib/monetra/active_support/whiny_nil.rb +38 -0
  92. data/test/test.rb +21 -0
  93. metadata +167 -0
@@ -0,0 +1,35 @@
1
+ require 'rake/testtask'
2
+ require 'rake/gempackagetask'
3
+
4
+ PKG_VERSION = '0.0.6'
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << "test"
8
+ t.test_files = Dir.glob('test/**/*_test.rb')
9
+ t.verbose = true
10
+ end
11
+
12
+ task :default => :test
13
+
14
+ dist_dirs = ['lib', 'test']
15
+
16
+ spec = Gem::Specification.new do |s|
17
+ s.name = 'monetra-ruby'
18
+ s.version = PKG_VERSION
19
+ s.summary = "Ruby library for accessing Main Street Softwork's Monetra credit card processing system"
20
+ s.description = s.summary
21
+
22
+ # s.add_dependency('activesupport', '>= 1.3.1')
23
+ s.add_dependency('builder', '>= 1.2')
24
+
25
+ s.require_path = 'lib'
26
+ s.autorequire = 'monetra'
27
+
28
+ s.files = ['Rakefile']
29
+ dist_dirs.each do |dir|
30
+ s.files = s.files + Dir.glob("#{dir}/**/*").delete_if {|item| item.include?("\.svn") }
31
+ end
32
+ end
33
+
34
+ Rake::GemPackageTask.new(spec) do |pkg|
35
+ end
@@ -0,0 +1,239 @@
1
+ begin
2
+ require 'rubygems'
3
+ require_gem 'builder', '~> 1.2'
4
+ rescue LoadError
5
+ require 'builder'
6
+ end
7
+
8
+ begin
9
+ require_gem 'activesupport', '~> 1.5'
10
+ rescue LoadError
11
+ require File.dirname(__FILE__) + '/monetra/active_support'
12
+ end
13
+
14
+ require 'csv'
15
+ require 'net/http'
16
+ require 'net/https'
17
+
18
+ #-----------------------------------------------------
19
+ module Monetra
20
+ class TransactionFailed < Exception
21
+ end
22
+
23
+ class ParserError < Exception
24
+ end
25
+
26
+ #-----------------------------------------------------
27
+ class Base
28
+ cattr_accessor :host, :port, :scheme, :username, :password, :debug
29
+ attr_accessor :options
30
+
31
+ #-----------------------------------------------------
32
+ def self.parse_uri(uri)
33
+ url = URI.parse(uri)
34
+ @options ||= {}
35
+ url.scheme ||= @options[:scheme] || scheme
36
+ url.host ||= @options[:host] || host
37
+ url.port ||= @options[:port] || port
38
+
39
+ return url
40
+ end
41
+
42
+ #-----------------------------------------------------
43
+ def self.post(body, uri='/')
44
+ url = self.parse_uri(uri)
45
+
46
+ req = Net::HTTP::Post.new(url.path, {'Content-Type' => 'text/xml', 'Accept' => 'text/xml'})
47
+ httpobj = Net::HTTP.new(url.host, url.port)
48
+ httpobj.use_ssl = true if url.scheme == 'https'
49
+
50
+ httpobj.start do |http|
51
+ http.request(req, body)
52
+ end
53
+ end
54
+
55
+ #-----------------------------------------------------
56
+ def self.send(trans_set)
57
+ if trans_set.class.to_s != 'Array'
58
+ trans_set = [trans_set]
59
+ end
60
+ xml = ''
61
+ x = 0
62
+ b = Builder::XmlMarkup.new(:target => xml, :indent => 2)
63
+ b.instruct!
64
+ b.MonetraTrans {|xx|
65
+ trans_set.each do |trans|
66
+ x += 1
67
+ xx.Trans(:identifier => x) do |t|
68
+ t.username(trans.options[:username] || trans.username)
69
+ t.password(trans.options[:password] || trans.password)
70
+ trans.to_h.each do |k,v|
71
+ t.tag!(k,v)
72
+ end
73
+ end
74
+ end
75
+ }
76
+ puts xml if @@debug == true
77
+ response, body = Monetra::Base.post(xml)
78
+ begin
79
+ resp = Hash.from_xml(body)
80
+ rescue
81
+ raise Monetra::ParserError(body)
82
+ end
83
+
84
+ raise Monetra::TransactionFailed(body) if resp['MonetraResp']['DataTransferStatus']['code'].upcase != 'SUCCESS'
85
+
86
+ resp_set = []
87
+ if resp['MonetraResp']['Resp'].is_a?(Hash)
88
+ rs = [resp['MonetraResp']['Resp']]
89
+ else
90
+ rs = resp['MonetraResp']['Resp']
91
+ end
92
+ rs.each do |data|
93
+ resp_set << Monetra::Response.new(data)
94
+ end
95
+ return resp_set
96
+ end
97
+
98
+ #-----------------------------------------------------
99
+ def to_h
100
+ @options
101
+ end ## initialize
102
+
103
+ end ## Base
104
+
105
+ #-----------------------------------------------------
106
+ module Admin
107
+
108
+ end ## Admin
109
+
110
+ #-----------------------------------------------------
111
+ module Transaction
112
+ #-----------------------------------------------------
113
+ class Base < Monetra::Base
114
+ #-----------------------------------------------------
115
+ def initialize(*args)
116
+ @options = args.last.is_a?(Hash) ? args.pop : {}
117
+ @options.merge!({:action => self.class.to_s.split('::')[-1].upcase})
118
+ end ## initialize
119
+
120
+ end ## Base
121
+
122
+ #-----------------------------------------------------
123
+ class Preauth < Base
124
+ @required_options = [:account, :amount, :expdate]
125
+ end ## Preauth
126
+
127
+ #-----------------------------------------------------
128
+ class Sale < Base
129
+ @required_options = [:account, :amount, :expdate]
130
+ end ## Sale
131
+
132
+ #-----------------------------------------------------
133
+ class PreauthComplete < Base
134
+ @required_options = [:ttid, :amount]
135
+ end ## PreauthComplete
136
+
137
+ #-----------------------------------------------------
138
+ class Return < Base
139
+ @required_options = [:ttid, :amount, :account, :expdate]
140
+ end ## Return
141
+
142
+ #-----------------------------------------------------
143
+ class Admin < Base
144
+ @required_options = [:admin]
145
+ end ## Admin
146
+
147
+ #-----------------------------------------------------
148
+ class Settle < Base
149
+ @required_options = [:batch]
150
+ end ## Settle
151
+
152
+ #-----------------------------------------------------
153
+ class Void < Base
154
+ @required_options = [:ttid]
155
+ end ## Void
156
+
157
+
158
+ end ## Transaction
159
+
160
+ #-----------------------------------------------------
161
+ class Response
162
+ #-----------------------------------------------------
163
+ def initialize(*args)
164
+ @options = args.last.is_a?(Hash) ? args.pop : {}
165
+ end ## initialize
166
+
167
+ #-----------------------------------------------------
168
+ def method_missing(methId)
169
+ @options[methId.to_s]
170
+ end ## method_missing
171
+
172
+ #-----------------------------------------------------
173
+ def success?
174
+ phard_code.upcase == 'SUCCESS'
175
+ end ## success?
176
+
177
+ #-----------------------------------------------------
178
+ def timestamp
179
+ Time.at(data['timestamp'].to_i)
180
+ end ## timestamp
181
+
182
+ end ## Response
183
+
184
+ #-----------------------------------------------------
185
+ module UserAudit
186
+ #-----------------------------------------------------
187
+ def self.ParseDataBlock(data)
188
+ rows = []
189
+ reader = CSV::Reader.create(data)
190
+ header = reader.shift
191
+ reader.each do |row|
192
+ h = {}
193
+ header.each_with_index do |k,i|
194
+ h[k] = row[i]
195
+ end
196
+ rows << h
197
+ end
198
+ return rows
199
+ end ## ParseDataBlock
200
+
201
+ #-----------------------------------------------------
202
+ def self.SendAdminReturnDataBlock(admin_value, options = {})
203
+ options.merge!({:admin => admin_value})
204
+ trans = Monetra::Transaction::Admin.new(options)
205
+ resp_set = Monetra::Base.send([trans])
206
+ pp resp_set
207
+ resp = resp_set[0]
208
+ pp resp if Monetra::Base.debug == true
209
+ self.ParseDataBlock(resp.DataBlock)
210
+ end ## SendAdminReturnDataBlock
211
+
212
+ #-----------------------------------------------------
213
+ def self.UnsettledTransactions(batch = nil)
214
+ self.SendAdminReturnDataBlock('GUT', {:batch => batch})
215
+ end ## GetUnseettledTransactions
216
+
217
+ #-----------------------------------------------------
218
+ def self.UnsettledBatches
219
+ self.SendAdminReturnDataBlock('UB')
220
+ end ## GetUnseettledBatches
221
+
222
+ #-----------------------------------------------------
223
+ def self.BatchTotals(batch = nil)
224
+ self.SendAdminReturnDataBlock('BT', {:batch => batch})
225
+ end ## BatchTotals
226
+
227
+ #-----------------------------------------------------
228
+ def self.SettledTransactions(batch = nil)
229
+ self.SendAdminReturnDataBlock('GL', {:batch => batch})
230
+ end ## TransactionLogs
231
+
232
+ #-----------------------------------------------------
233
+ def self.FailedTransactions
234
+ self.SendAdminReturnDataBlock('GFT')
235
+ end ## FailedTransactions
236
+
237
+ end ## UserAudit
238
+
239
+ end ## MCVE
@@ -0,0 +1,42 @@
1
+ #--
2
+ # Copyright (c) 2005 David Heinemeier Hansson
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ $:.unshift(File.dirname(__FILE__))
25
+ $:.unshift(File.dirname(__FILE__) + "/active_support/vendor")
26
+
27
+ require 'builder'
28
+
29
+ require 'active_support/inflector'
30
+
31
+ require 'active_support/core_ext'
32
+ require 'active_support/clean_logger'
33
+ require 'active_support/dependencies'
34
+ require 'active_support/reloadable'
35
+ require 'active_support/deprecation'
36
+
37
+ require 'active_support/ordered_options'
38
+ require 'active_support/option_merger'
39
+
40
+ require 'active_support/values/time_zone'
41
+
42
+ require 'active_support/json'
@@ -0,0 +1,84 @@
1
+ begin
2
+ require 'simplecc'
3
+ rescue LoadError
4
+ class Continuation # :nodoc: # for RDoc
5
+ end
6
+ def Continuation.create(*args, &block) # :nodoc:
7
+ cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?}
8
+ result ||= args
9
+ return *[cc, *result]
10
+ end
11
+ end
12
+
13
+ class Binding; end # for RDoc
14
+ # This method returns the binding of the method that called your
15
+ # method. It will raise an Exception when you're not inside a method.
16
+ #
17
+ # It's used like this:
18
+ # def inc_counter(amount = 1)
19
+ # Binding.of_caller do |binding|
20
+ # # Create a lambda that will increase the variable 'counter'
21
+ # # in the caller of this method when called.
22
+ # inc = eval("lambda { |arg| counter += arg }", binding)
23
+ # # We can refer to amount from inside this block safely.
24
+ # inc.call(amount)
25
+ # end
26
+ # # No other statements can go here. Put them inside the block.
27
+ # end
28
+ # counter = 0
29
+ # 2.times { inc_counter }
30
+ # counter # => 2
31
+ #
32
+ # Binding.of_caller must be the last statement in the method.
33
+ # This means that you will have to put everything you want to
34
+ # do after the call to Binding.of_caller into the block of it.
35
+ # This should be no problem however, because Ruby has closures.
36
+ # If you don't do this an Exception will be raised. Because of
37
+ # the way that Binding.of_caller is implemented it has to be
38
+ # done this way.
39
+ def Binding.of_caller(&block)
40
+ old_critical = Thread.critical
41
+ Thread.critical = true
42
+ count = 0
43
+ cc, result, error, extra_data = Continuation.create(nil, nil)
44
+ error.call if error
45
+
46
+ tracer = lambda do |*args|
47
+ type, context, extra_data = args[0], args[4], args
48
+ if type == "return"
49
+ count += 1
50
+ # First this method and then calling one will return --
51
+ # the trace event of the second event gets the context
52
+ # of the method which called the method that called this
53
+ # method.
54
+ if count == 2
55
+ # It would be nice if we could restore the trace_func
56
+ # that was set before we swapped in our own one, but
57
+ # this is impossible without overloading set_trace_func
58
+ # in current Ruby.
59
+ set_trace_func(nil)
60
+ cc.call(eval("binding", context), nil, extra_data)
61
+ end
62
+ elsif type == "line" then
63
+ nil
64
+ elsif type == "c-return" and extra_data[3] == :set_trace_func then
65
+ nil
66
+ else
67
+ set_trace_func(nil)
68
+ error_msg = "Binding.of_caller used in non-method context or " +
69
+ "trailing statements of method using it aren't in the block."
70
+ cc.call(nil, lambda { raise(ArgumentError, error_msg) }, nil)
71
+ end
72
+ end
73
+
74
+ unless result
75
+ set_trace_func(tracer)
76
+ return nil
77
+ else
78
+ Thread.critical = old_critical
79
+ case block.arity
80
+ when 1 then yield(result)
81
+ else yield(result, extra_data)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,523 @@
1
+ # The Breakpoint library provides the convenience of
2
+ # being able to inspect and modify state, diagnose
3
+ # bugs all via IRB by simply setting breakpoints in
4
+ # your applications by the call of a method.
5
+ #
6
+ # This library was written and is supported by me,
7
+ # Florian Gross. I can be reached at flgr@ccan.de
8
+ # and enjoy getting feedback about my libraries.
9
+ #
10
+ # The whole library (including breakpoint_client.rb
11
+ # and binding_of_caller.rb) is licensed under the
12
+ # same license that Ruby uses. (Which is currently
13
+ # either the GNU General Public License or a custom
14
+ # one that allows for commercial usage.) If you for
15
+ # some good reason need to use this under another
16
+ # license please contact me.
17
+
18
+ require 'irb'
19
+ require File.dirname(__FILE__) + '/binding_of_caller' unless defined? Binding.of_caller
20
+ require 'drb'
21
+ require 'drb/acl'
22
+
23
+ module Breakpoint
24
+ id = %q$Id: breakpoint.rb 92 2005-02-04 22:35:53Z flgr $
25
+ Version = id.split(" ")[2].to_i
26
+
27
+ extend self
28
+
29
+ # This will pop up an interactive ruby session at a
30
+ # pre-defined break point in a Ruby application. In
31
+ # this session you can examine the environment of
32
+ # the break point.
33
+ #
34
+ # You can get a list of variables in the context using
35
+ # local_variables via +local_variables+. You can then
36
+ # examine their values by typing their names.
37
+ #
38
+ # You can have a look at the call stack via +caller+.
39
+ #
40
+ # The source code around the location where the breakpoint
41
+ # was executed can be examined via +source_lines+. Its
42
+ # argument specifies how much lines of context to display.
43
+ # The default amount of context is 5 lines. Note that
44
+ # the call to +source_lines+ can raise an exception when
45
+ # it isn't able to read in the source code.
46
+ #
47
+ # breakpoints can also return a value. They will execute
48
+ # a supplied block for getting a default return value.
49
+ # A custom value can be returned from the session by doing
50
+ # +throw(:debug_return, value)+.
51
+ #
52
+ # You can also give names to break points which will be
53
+ # used in the message that is displayed upon execution
54
+ # of them.
55
+ #
56
+ # Here's a sample of how breakpoints should be placed:
57
+ #
58
+ # class Person
59
+ # def initialize(name, age)
60
+ # @name, @age = name, age
61
+ # breakpoint("Person#initialize")
62
+ # end
63
+ #
64
+ # attr_reader :age
65
+ # def name
66
+ # breakpoint("Person#name") { @name }
67
+ # end
68
+ # end
69
+ #
70
+ # person = Person.new("Random Person", 23)
71
+ # puts "Name: #{person.name}"
72
+ #
73
+ # And here is a sample debug session:
74
+ #
75
+ # Executing break point "Person#initialize" at file.rb:4 in `initialize'
76
+ # irb(#<Person:0x292fbe8>):001:0> local_variables
77
+ # => ["name", "age", "_", "__"]
78
+ # irb(#<Person:0x292fbe8>):002:0> [name, age]
79
+ # => ["Random Person", 23]
80
+ # irb(#<Person:0x292fbe8>):003:0> [@name, @age]
81
+ # => ["Random Person", 23]
82
+ # irb(#<Person:0x292fbe8>):004:0> self
83
+ # => #<Person:0x292fbe8 @age=23, @name="Random Person">
84
+ # irb(#<Person:0x292fbe8>):005:0> @age += 1; self
85
+ # => #<Person:0x292fbe8 @age=24, @name="Random Person">
86
+ # irb(#<Person:0x292fbe8>):006:0> exit
87
+ # Executing break point "Person#name" at file.rb:9 in `name'
88
+ # irb(#<Person:0x292fbe8>):001:0> throw(:debug_return, "Overriden name")
89
+ # Name: Overriden name
90
+ #
91
+ # Breakpoint sessions will automatically have a few
92
+ # convenience methods available. See Breakpoint::CommandBundle
93
+ # for a list of them.
94
+ #
95
+ # Breakpoints can also be used remotely over sockets.
96
+ # This is implemented by running part of the IRB session
97
+ # in the application and part of it in a special client.
98
+ # You have to call Breakpoint.activate_drb to enable
99
+ # support for remote breakpoints and then run
100
+ # breakpoint_client.rb which is distributed with this
101
+ # library. See the documentation of Breakpoint.activate_drb
102
+ # for details.
103
+ def breakpoint(id = nil, context = nil, &block)
104
+ callstack = caller
105
+ callstack.slice!(0, 3) if callstack.first["breakpoint"]
106
+ file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures
107
+
108
+ message = "Executing break point " + (id ? "#{id.inspect} " : "") +
109
+ "at #{file}:#{line}" + (method ? " in `#{method}'" : "")
110
+
111
+ if context then
112
+ return handle_breakpoint(context, message, file, line, &block)
113
+ end
114
+
115
+ Binding.of_caller do |binding_context|
116
+ handle_breakpoint(binding_context, message, file, line, &block)
117
+ end
118
+ end
119
+
120
+ module CommandBundle #:nodoc:
121
+ # Proxy to a Breakpoint client. Lets you directly execute code
122
+ # in the context of the client.
123
+ class Client #:nodoc:
124
+ def initialize(eval_handler) # :nodoc:
125
+ eval_handler.untaint
126
+ @eval_handler = eval_handler
127
+ end
128
+
129
+ instance_methods.each do |method|
130
+ next if method[/^__.+__$/]
131
+ undef_method method
132
+ end
133
+
134
+ # Executes the specified code at the client.
135
+ def eval(code)
136
+ @eval_handler.call(code)
137
+ end
138
+
139
+ # Will execute the specified statement at the client.
140
+ def method_missing(method, *args, &block)
141
+ if args.empty? and not block
142
+ result = eval "#{method}"
143
+ else
144
+ # This is a bit ugly. The alternative would be using an
145
+ # eval context instead of an eval handler for executing
146
+ # the code at the client. The problem with that approach
147
+ # is that we would have to handle special expressions
148
+ # like "self", "nil" or constants ourself which is hard.
149
+ remote = eval %{
150
+ result = lambda { |block, *args| #{method}(*args, &block) }
151
+ def result.call_with_block(*args, &block)
152
+ call(block, *args)
153
+ end
154
+ result
155
+ }
156
+ remote.call_with_block(*args, &block)
157
+ end
158
+
159
+ return result
160
+ end
161
+ end
162
+
163
+ # Returns the source code surrounding the location where the
164
+ # breakpoint was issued.
165
+ def source_lines(context = 5, return_line_numbers = false)
166
+ lines = File.readlines(@__bp_file).map { |line| line.chomp }
167
+
168
+ break_line = @__bp_line
169
+ start_line = [break_line - context, 1].max
170
+ end_line = break_line + context
171
+
172
+ result = lines[(start_line - 1) .. (end_line - 1)]
173
+
174
+ if return_line_numbers then
175
+ return [start_line, break_line, result]
176
+ else
177
+ return result
178
+ end
179
+ end
180
+
181
+ # Lets an object that will forward method calls to the breakpoint
182
+ # client. This is useful for outputting longer things at the client
183
+ # and so on. You can for example do these things:
184
+ #
185
+ # client.puts "Hello" # outputs "Hello" at client console
186
+ # # outputs "Hello" into the file temp.txt at the client
187
+ # client.File.open("temp.txt", "w") { |f| f.puts "Hello" }
188
+ def client()
189
+ if Breakpoint.use_drb? then
190
+ sleep(0.5) until Breakpoint.drb_service.eval_handler
191
+ Client.new(Breakpoint.drb_service.eval_handler)
192
+ else
193
+ Client.new(lambda { |code| eval(code, TOPLEVEL_BINDING) })
194
+ end
195
+ end
196
+ end
197
+
198
+ def handle_breakpoint(context, message, file = "", line = "", &block) # :nodoc:
199
+ catch(:debug_return) do |value|
200
+ eval(%{
201
+ @__bp_file = #{file.inspect}
202
+ @__bp_line = #{line}
203
+ extend Breakpoint::CommandBundle
204
+ extend DRbUndumped if self
205
+ }, context) rescue nil
206
+
207
+ if not use_drb? then
208
+ puts message
209
+ IRB.start(nil, IRB::WorkSpace.new(context))
210
+ else
211
+ @drb_service.add_breakpoint(context, message)
212
+ end
213
+
214
+ block.call if block
215
+ end
216
+ end
217
+
218
+ # These exceptions will be raised on failed asserts
219
+ # if Breakpoint.asserts_cause_exceptions is set to
220
+ # true.
221
+ class FailedAssertError < RuntimeError #:nodoc:
222
+ end
223
+
224
+ # This asserts that the block evaluates to true.
225
+ # If it doesn't evaluate to true a breakpoint will
226
+ # automatically be created at that execution point.
227
+ #
228
+ # You can disable assert checking in production
229
+ # code by setting Breakpoint.optimize_asserts to
230
+ # true. (It will still be enabled when Ruby is run
231
+ # via the -d argument.)
232
+ #
233
+ # Example:
234
+ # person_name = "Foobar"
235
+ # assert { not person_name.nil? }
236
+ #
237
+ # Note: If you want to use this method from an
238
+ # unit test, you will have to call it by its full
239
+ # name, Breakpoint.assert.
240
+ def assert(context = nil, &condition)
241
+ return if Breakpoint.optimize_asserts and not $DEBUG
242
+ return if yield
243
+
244
+ callstack = caller
245
+ callstack.slice!(0, 3) if callstack.first["assert"]
246
+ file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures
247
+
248
+ message = "Assert failed at #{file}:#{line}#{" in `#{method}'" if method}."
249
+
250
+ if Breakpoint.asserts_cause_exceptions and not $DEBUG then
251
+ raise(Breakpoint::FailedAssertError, message)
252
+ end
253
+
254
+ message += " Executing implicit breakpoint."
255
+
256
+ if context then
257
+ return handle_breakpoint(context, message, file, line)
258
+ end
259
+
260
+ Binding.of_caller do |context|
261
+ handle_breakpoint(context, message, file, line)
262
+ end
263
+ end
264
+
265
+ # Whether asserts should be ignored if not in debug mode.
266
+ # Debug mode can be enabled by running ruby with the -d
267
+ # switch or by setting $DEBUG to true.
268
+ attr_accessor :optimize_asserts
269
+ self.optimize_asserts = false
270
+
271
+ # Whether an Exception should be raised on failed asserts
272
+ # in non-$DEBUG code or not. By default this is disabled.
273
+ attr_accessor :asserts_cause_exceptions
274
+ self.asserts_cause_exceptions = false
275
+ @use_drb = false
276
+
277
+ attr_reader :drb_service # :nodoc:
278
+
279
+ class DRbService # :nodoc:
280
+ include DRbUndumped
281
+
282
+ def initialize
283
+ @handler = @eval_handler = @collision_handler = nil
284
+
285
+ IRB.instance_eval { @CONF[:RC] = true }
286
+ IRB.run_config
287
+ end
288
+
289
+ def collision
290
+ sleep(0.5) until @collision_handler
291
+
292
+ @collision_handler.untaint
293
+
294
+ @collision_handler.call
295
+ end
296
+
297
+ def ping() end
298
+
299
+ def add_breakpoint(context, message)
300
+ workspace = IRB::WorkSpace.new(context)
301
+ workspace.extend(DRbUndumped)
302
+
303
+ sleep(0.5) until @handler
304
+
305
+ @handler.untaint
306
+ @handler.call(workspace, message)
307
+ end
308
+
309
+ attr_accessor :handler, :eval_handler, :collision_handler
310
+ end
311
+
312
+ # Will run Breakpoint in DRb mode. This will spawn a server
313
+ # that can be attached to via the breakpoint-client command
314
+ # whenever a breakpoint is executed. This is useful when you
315
+ # are debugging CGI applications or other applications where
316
+ # you can't access debug sessions via the standard input and
317
+ # output of your application.
318
+ #
319
+ # You can specify an URI where the DRb server will run at.
320
+ # This way you can specify the port the server runs on. The
321
+ # default URI is druby://localhost:42531.
322
+ #
323
+ # Please note that breakpoints will be skipped silently in
324
+ # case the DRb server can not spawned. (This can happen if
325
+ # the port is already used by another instance of your
326
+ # application on CGI or another application.)
327
+ #
328
+ # Also note that by default this will only allow access
329
+ # from localhost. You can however specify a list of
330
+ # allowed hosts or nil (to allow access from everywhere).
331
+ # But that will still not protect you from somebody
332
+ # reading the data as it goes through the net.
333
+ #
334
+ # A good approach for getting security and remote access
335
+ # is setting up an SSH tunnel between the DRb service
336
+ # and the client. This is usually done like this:
337
+ #
338
+ # $ ssh -L20000:127.0.0.1:20000 -R10000:127.0.0.1:10000 example.com
339
+ # (This will connect port 20000 at the client side to port
340
+ # 20000 at the server side, and port 10000 at the server
341
+ # side to port 10000 at the client side.)
342
+ #
343
+ # After that do this on the server side: (the code being debugged)
344
+ # Breakpoint.activate_drb("druby://127.0.0.1:20000", "localhost")
345
+ #
346
+ # And at the client side:
347
+ # ruby breakpoint_client.rb -c druby://127.0.0.1:10000 -s druby://127.0.0.1:20000
348
+ #
349
+ # Running through such a SSH proxy will also let you use
350
+ # breakpoint.rb in case you are behind a firewall.
351
+ #
352
+ # Detailed information about running DRb through firewalls is
353
+ # available at http://www.rubygarden.org/ruby?DrbTutorial
354
+ def activate_drb(uri = nil, allowed_hosts = ['localhost', '127.0.0.1', '::1'],
355
+ ignore_collisions = false)
356
+
357
+ return false if @use_drb
358
+
359
+ uri ||= 'druby://localhost:42531'
360
+
361
+ if allowed_hosts then
362
+ acl = ["deny", "all"]
363
+
364
+ Array(allowed_hosts).each do |host|
365
+ acl += ["allow", host]
366
+ end
367
+
368
+ DRb.install_acl(ACL.new(acl))
369
+ end
370
+
371
+ @use_drb = true
372
+ @drb_service = DRbService.new
373
+ did_collision = false
374
+ begin
375
+ @service = DRb.start_service(uri, @drb_service)
376
+ rescue Errno::EADDRINUSE
377
+ if ignore_collisions then
378
+ nil
379
+ else
380
+ # The port is already occupied by another
381
+ # Breakpoint service. We will try to tell
382
+ # the old service that we want its port.
383
+ # It will then forward that request to the
384
+ # user and retry.
385
+ unless did_collision then
386
+ DRbObject.new(nil, uri).collision
387
+ did_collision = true
388
+ end
389
+ sleep(10)
390
+ retry
391
+ end
392
+ end
393
+
394
+ return true
395
+ end
396
+
397
+ # Deactivates a running Breakpoint service.
398
+ def deactivate_drb
399
+ @service.stop_service unless @service.nil?
400
+ @service = nil
401
+ @use_drb = false
402
+ @drb_service = nil
403
+ end
404
+
405
+ # Returns true when Breakpoints are used over DRb.
406
+ # Breakpoint.activate_drb causes this to be true.
407
+ def use_drb?
408
+ @use_drb == true
409
+ end
410
+ end
411
+
412
+ module IRB #:nodoc:
413
+ class << self; remove_method :start; end
414
+ def self.start(ap_path = nil, main_context = nil, workspace = nil)
415
+ $0 = File::basename(ap_path, ".rb") if ap_path
416
+
417
+ # suppress some warnings about redefined constants
418
+ old_verbose, $VERBOSE = $VERBOSE, nil
419
+ IRB.setup(ap_path)
420
+ $VERBOSE = old_verbose
421
+
422
+ if @CONF[:SCRIPT] then
423
+ irb = Irb.new(main_context, @CONF[:SCRIPT])
424
+ else
425
+ irb = Irb.new(main_context)
426
+ end
427
+
428
+ if workspace then
429
+ irb.context.workspace = workspace
430
+ end
431
+
432
+ @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
433
+ @CONF[:MAIN_CONTEXT] = irb.context
434
+
435
+ old_sigint = trap("SIGINT") do
436
+ begin
437
+ irb.signal_handle
438
+ rescue RubyLex::TerminateLineInput
439
+ # ignored
440
+ end
441
+ end
442
+
443
+ catch(:IRB_EXIT) do
444
+ irb.eval_input
445
+ end
446
+ ensure
447
+ trap("SIGINT", old_sigint)
448
+ end
449
+
450
+ class << self
451
+ alias :old_CurrentContext :CurrentContext
452
+ remove_method :CurrentContext
453
+ end
454
+ def IRB.CurrentContext
455
+ if old_CurrentContext.nil? and Breakpoint.use_drb? then
456
+ result = Object.new
457
+ def result.last_value; end
458
+ return result
459
+ else
460
+ old_CurrentContext
461
+ end
462
+ end
463
+ def IRB.parse_opts() end
464
+
465
+ class Context #:nodoc:
466
+ alias :old_evaluate :evaluate
467
+ def evaluate(line, line_no)
468
+ if line.chomp == "exit" then
469
+ exit
470
+ else
471
+ old_evaluate(line, line_no)
472
+ end
473
+ end
474
+ end
475
+
476
+ class WorkSpace #:nodoc:
477
+ alias :old_evaluate :evaluate
478
+
479
+ def evaluate(*args)
480
+ if Breakpoint.use_drb? then
481
+ result = old_evaluate(*args)
482
+ if args[0] != :no_proxy and
483
+ not [true, false, nil].include?(result)
484
+ then
485
+ result.extend(DRbUndumped) rescue nil
486
+ end
487
+ return result
488
+ else
489
+ old_evaluate(*args)
490
+ end
491
+ end
492
+ end
493
+
494
+ module InputCompletor #:nodoc:
495
+ def self.eval(code, context, *more)
496
+ # Big hack, this assumes that InputCompletor
497
+ # will only call eval() when it wants code
498
+ # to be executed in the IRB context.
499
+ IRB.conf[:MAIN_CONTEXT].workspace.evaluate(:no_proxy, code, *more)
500
+ end
501
+ end
502
+ end
503
+
504
+ module DRb # :nodoc:
505
+ class DRbObject #:nodoc:
506
+ undef :inspect if method_defined?(:inspect)
507
+ undef :clone if method_defined?(:clone)
508
+ end
509
+ end
510
+
511
+ # See Breakpoint.breakpoint
512
+ def breakpoint(id = nil, &block)
513
+ Binding.of_caller do |context|
514
+ Breakpoint.breakpoint(id, context, &block)
515
+ end
516
+ end
517
+
518
+ # See Breakpoint.assert
519
+ def assert(&block)
520
+ Binding.of_caller do |context|
521
+ Breakpoint.assert(context, &block)
522
+ end
523
+ end