jmx4r 0.0.8 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.rdoc CHANGED
@@ -29,6 +29,11 @@ jmx4r helps to manage Java applications using JMX in a simple and powerful way:
29
29
  # trigger a Garbage Collection
30
30
  memory.gc
31
31
 
32
+ # For local processes not publishing jmxrmi ports, instead:
33
+
34
+ # connect to the local JConsole process
35
+ JMX::MBean.establish_connection :command => /jconsole/i
36
+
32
37
  == Help
33
38
 
34
39
  * Wiki[http://jmesnil.net/wiki/Jmx4r]
data/Rakefile CHANGED
@@ -6,7 +6,7 @@ require "rubygems"
6
6
 
7
7
  dir = File.dirname(__FILE__)
8
8
  lib = File.join(dir, "lib", "jmx4r.rb")
9
- version = "0.0.8"
9
+ version = "0.1.0"
10
10
 
11
11
  task :default => [:test]
12
12
 
data/lib/dynamic_mbean.rb CHANGED
@@ -3,10 +3,10 @@
3
3
  # taken from the 'jmx' gems in jruby-extras
4
4
 
5
5
  module JMX
6
- import javax.management.MBeanParameterInfo
7
- import javax.management.MBeanOperationInfo
8
- import javax.management.MBeanAttributeInfo
9
- import javax.management.MBeanInfo
6
+ java_import javax.management.MBeanParameterInfo
7
+ java_import javax.management.MBeanOperationInfo
8
+ java_import javax.management.MBeanAttributeInfo
9
+ java_import javax.management.MBeanInfo
10
10
 
11
11
  # Module that is used to bridge java to ruby and ruby to java types.
12
12
  module JavaTypeAware
@@ -99,32 +99,36 @@ module JMX
99
99
  # like: user_name= and username. So in your ruby code you can treat the attributes
100
100
  # as "regular" ruby accessors
101
101
  class DynamicMBean
102
- import javax.management.MBeanOperationInfo
103
- import javax.management.MBeanAttributeInfo
104
- import javax.management.DynamicMBean
105
- import javax.management.MBeanInfo
102
+ java_import javax.management.MBeanOperationInfo
103
+ java_import javax.management.MBeanAttributeInfo
104
+ java_import javax.management.DynamicMBean
105
+ java_import javax.management.MBeanInfo
106
106
  include JMX::JavaTypeAware
107
107
 
108
108
  #NOTE this will not be needed when JRuby-3164 is fixed.
109
109
  def self.inherited(cls)
110
110
  cls.send(:include, DynamicMBean)
111
111
  end
112
+
113
+ def self.mbean_attributes
114
+ @mbean_attributes ||= {}
115
+ end
112
116
 
113
117
  # TODO: preserve any original method_added?
114
118
  # TODO: Error handling here when it all goes wrong?
115
119
  def self.method_added(name) #:nodoc:
116
- return if Thread.current[:op].nil?
117
- Thread.current[:op].name = name
118
- operations << Thread.current[:op].to_jmx
119
- Thread.current[:op] = nil
120
+ return if self.mbean_attributes[:op].nil?
121
+ self.mbean_attributes[:op].name = name
122
+ operations << self.mbean_attributes[:op].to_jmx
123
+ self.mbean_attributes[:op] = nil
120
124
  end
121
125
 
122
126
  def self.attributes #:nodoc:
123
- Thread.current[:attrs] ||= []
127
+ self.mbean_attributes[:attrs] ||= []
124
128
  end
125
129
 
126
130
  def self.operations #:nodoc:
127
- Thread.current[:ops] ||= []
131
+ self.mbean_attributes[:ops] ||= []
128
132
  end
129
133
 
130
134
  # the <tt>rw_attribute</tt> method is used to declare a JMX read write attribute.
@@ -207,7 +211,7 @@ module JMX
207
211
  #++
208
212
  def self.operation(description=nil)
209
213
  # Wait to error check until method_added so we can know method name
210
- Thread.current[:op] = JMX::Operation.new description
214
+ self.mbean_attributes[:op] = JMX::Operation.new description
211
215
  end
212
216
 
213
217
  # Used to declare a parameter (you can declare more than one in succession) that
@@ -220,7 +224,7 @@ module JMX
220
224
  # ...
221
225
  # end
222
226
  def self.parameter(type, name=nil, description=nil)
223
- Thread.current[:op].parameters << JMX::Parameter.new(type, name, description)
227
+ self.mbean_attributes[:op].parameters << JMX::Parameter.new(type, name, description)
224
228
  end
225
229
 
226
230
  # Used to declare the return type of the operation
@@ -231,7 +235,7 @@ module JMX
231
235
  # ...
232
236
  # end
233
237
  def self.returns(type)
234
- Thread.current[:op].return_type = type
238
+ self.mbean_attributes[:op].return_type = type
235
239
  end
236
240
 
237
241
  def initialize(description="")
@@ -272,4 +276,3 @@ module JMX
272
276
  end
273
277
 
274
278
  end
275
-
data/lib/jconsole.rb CHANGED
@@ -12,7 +12,8 @@ module JConsole
12
12
  # By default, no authentication is required to connect to it.
13
13
  #
14
14
  # The args hash accepts 3 keys:
15
- # [:port] the port which will be listens to JMX connections
15
+ # [:port] the port which will be listens to JMX connections.
16
+ # if the port is 0, jmxrmi port is not published
16
17
  # [:pwd_file] the path to the file containing the authentication credentials
17
18
  # [:access_file] the path to the file containing the authorization credentials
18
19
  #
@@ -29,13 +30,19 @@ module JConsole
29
30
  cmd =<<-EOCMD.split("\n").join(" ")
30
31
  jconsole
31
32
  -J-Dcom.sun.management.jmxremote
32
- -J-Dcom.sun.management.jmxremote.port=#{port}
33
- -J-Dcom.sun.management.jmxremote.ssl=false
34
- -J-Dcom.sun.management.jmxremote.authenticate=#{!pwd_file.nil?}
35
- EOCMD
36
- if pwd_file and access_file
37
- cmd << " -J-Dcom.sun.management.jmxremote.password.file=#{pwd_file}"
38
- cmd << " -J-Dcom.sun.management.jmxremote.access.file=#{access_file}"
33
+ EOCMD
34
+
35
+ if port != 0
36
+ cmd << <<-EOCMD.split("\n").join(" ")
37
+ -J-Dcom.sun.management.jmxremote.port=#{port}
38
+ -J-Dcom.sun.management.jmxremote.ssl=false
39
+ -J-Dcom.sun.management.jmxremote.authenticate=#{!pwd_file.nil?}
40
+ EOCMD
41
+
42
+ if pwd_file and access_file
43
+ cmd << " -J-Dcom.sun.management.jmxremote.password.file=#{pwd_file}"
44
+ cmd << " -J-Dcom.sun.management.jmxremote.access.file=#{access_file}"
45
+ end
39
46
  end
40
47
  Thread.start { system cmd }
41
48
  sleep 3
@@ -46,7 +53,11 @@ EOCMD
46
53
  # By default, it will kill the process corresponding to an instance JConsole with
47
54
  # a port on 3000. Another port can be specified in parameter.
48
55
  def JConsole.stop(port=3000)
49
- jconsole_pid = `ps a -w -o pid,command | grep -w jconsole | grep port=#{port} | grep -v grep | grep -v ruby | cut -c -5`
56
+ ps = "ps a -w -o pid,command | grep -w jconsole"
57
+ ps << " | grep port=#{port}" if port != 0
58
+ ps << " | grep -v grep | grep -v ruby | cut -c -5"
59
+
60
+ jconsole_pid = `#{ps}`
50
61
  `kill #{jconsole_pid}` if jconsole_pid != ""
51
62
  sleep 1
52
63
  end
data/lib/jdk/jdk4.rb ADDED
@@ -0,0 +1,16 @@
1
+
2
+ module JMX
3
+ module JDKHelper
4
+ module JDK4
5
+
6
+ class << self
7
+ def method_missing(method, *args, &block)
8
+ raise "JDK (>= 5.0) implementation is not available - \
9
+ maybe only JREs or older JDKs are installed properly."
10
+ end
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+
data/lib/jdk/jdk5.rb ADDED
@@ -0,0 +1,34 @@
1
+
2
+ module JMX
3
+ module JDKHelper
4
+ module JDK5
5
+ include_class 'sun.jvmstat.monitor.HostIdentifier'
6
+ include_class 'sun.jvmstat.monitor.MonitoredHost'
7
+ include_class 'sun.jvmstat.monitor.MonitoredVmUtil'
8
+ include_class 'sun.jvmstat.monitor.VmIdentifier'
9
+ include_class 'sun.management.ConnectorAddressLink'
10
+
11
+ class << self
12
+
13
+ def find_local_url(command_pattern)
14
+ host_id = HostIdentifier.new(nil)
15
+ host = MonitoredHost.get_monitored_host(host_id)
16
+
17
+ host.active_vms.each do |vmid_int|
18
+ vmid = VmIdentifier.new(vmid_int.to_s)
19
+ vm = host.get_monitored_vm(vmid)
20
+ command = MonitoredVmUtil.command_line(vm)
21
+ if command_pattern === command
22
+ return ConnectorAddressLink.import_from(vmid_int)
23
+ end
24
+ end
25
+
26
+ nil
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+
data/lib/jdk/jdk6.rb ADDED
@@ -0,0 +1,69 @@
1
+
2
+ module JMX
3
+ module JDKHelper
4
+ module JDK6
5
+ include_class 'com.sun.tools.attach.VirtualMachine'
6
+
7
+ class << self
8
+ def find_local_url(command_pattern)
9
+ target_vmd = VirtualMachine.list.find do |vmd|
10
+ command_pattern === vmd.display_name
11
+ end
12
+
13
+ if target_vmd
14
+ local_connector_address(target_vmd)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def local_connector_address(vm_descriptor)
21
+ vm = VirtualMachine.attach(vm_descriptor)
22
+
23
+ address = nil
24
+ agent_loaded = false
25
+
26
+ lambda {
27
+ address = vm.get_agent_properties.get(
28
+ "com.sun.management.jmxremote.localConnectorAddress")
29
+
30
+ unless address || agent_loaded
31
+ load_management_agent(vm)
32
+ agent_loaded = true
33
+ redo
34
+ end
35
+ }.call
36
+
37
+ vm.detach
38
+
39
+ address
40
+ end
41
+
42
+ def load_management_agent(vm)
43
+ home =
44
+ vm.get_system_properties.get_property 'java.home'
45
+
46
+ try_load_management_agent(vm, [home, 'jre', 'lib']) or
47
+ try_load_management_agent(vm, [home, 'lib']) or
48
+ raise "management agent not found"
49
+ end
50
+
51
+ def try_load_management_agent(vm, path)
52
+ sep = vm.get_system_properties.get_property 'file.separator'
53
+
54
+ path = path.dup
55
+ path << 'management-agent.jar'
56
+
57
+ file = Java::java.io.File.new(path.join(sep))
58
+ if file.exists
59
+ vm.load_agent(file.get_canonical_path,
60
+ "com.sun.management.jmxremote")
61
+ true
62
+ end
63
+ end
64
+
65
+ end
66
+ end
67
+ end
68
+ end
69
+
data/lib/jdk_helper.rb ADDED
@@ -0,0 +1,69 @@
1
+
2
+ module JMX
3
+ module JDKHelper
4
+ include_class 'java.lang.System'
5
+
6
+ class << self
7
+
8
+ def method_missing(method, *args, &block)
9
+ init unless @jdk
10
+ @jdk.send method, *args, &block
11
+ end
12
+
13
+ private
14
+
15
+ def init
16
+ @jdk =
17
+ case
18
+ when has_java_class?("com.sun.tools.attach.VirtualMachine")
19
+ require "jdk/jdk6"
20
+ JDK6
21
+ when has_java_class?('sun.jvmstat.monitor.MonitoredHost')
22
+ require "jdk/jdk5"
23
+ JDK5
24
+ else
25
+ require "jdk/jdk4"
26
+ JDK4
27
+ end
28
+ end
29
+
30
+ def has_java_class?(name)
31
+ begin
32
+ include_class name
33
+ true
34
+ rescue
35
+ retry if load_tools_jar
36
+ false
37
+ end
38
+ end
39
+
40
+ def load_tools_jar
41
+ unless @tools_loaded
42
+ home = System.get_property 'java.home'
43
+ paths = [
44
+ [home, '..', 'lib'],
45
+ [home, 'lib'],
46
+ ]
47
+ try_load_jar('tools.jar', paths)
48
+ @tools_loaded = true
49
+ true
50
+ end
51
+ end
52
+
53
+ def try_load_jar(jar_file, paths)
54
+ sep = System.get_property 'file.separator'
55
+ paths = paths.dup
56
+ begin
57
+ path = paths.shift
58
+ require path.join(sep) + sep + jar_file
59
+ true
60
+ rescue LoadError
61
+ retry unless paths.empty?
62
+ false
63
+ end
64
+ end
65
+ end
66
+
67
+ end
68
+ end
69
+
data/lib/jmx4r.rb CHANGED
@@ -18,6 +18,7 @@ module JMX
18
18
  require 'dynamic_mbean'
19
19
  require 'open_data_helper'
20
20
  require 'objectname_helper'
21
+ require 'jdk_helper'
21
22
  require 'jruby'
22
23
 
23
24
  class MBeanServerConnectionProxy
@@ -57,6 +58,13 @@ module JMX
57
58
 
58
59
  attr_reader :object_name, :operations, :attributes, :connection
59
60
 
61
+ def metaclass; class << self; self; end; end
62
+ def meta_def name, &blk
63
+ metaclass.instance_eval do
64
+ define_method name, &blk
65
+ end
66
+ end
67
+
60
68
  # Creates a new MBean.
61
69
  #
62
70
  # object_name:: a string corresponding to a valid ObjectName
@@ -70,19 +78,6 @@ module JMX
70
78
  @attributes = Hash.new
71
79
  info.attributes.each do | mbean_attr |
72
80
  @attributes[mbean_attr.name.snake_case] = mbean_attr.name
73
- self.class.instance_eval do
74
- define_method mbean_attr.name.snake_case do
75
- @connection.getAttribute @object_name, "#{mbean_attr.name}"
76
- end
77
- end
78
- if mbean_attr.isWritable
79
- self.class.instance_eval do
80
- define_method "#{mbean_attr.name.snake_case}=" do |value|
81
- attr = Attribute.new mbean_attr.name, value
82
- @connection.setAttribute @object_name, attr
83
- end
84
- end
85
- end
86
81
  end
87
82
  @operations = Hash.new
88
83
  info.operations.each do |mbean_op|
@@ -92,12 +87,14 @@ module JMX
92
87
  end
93
88
 
94
89
  def method_missing(method, *args, &block) #:nodoc:
95
- if @operations.keys.include?(method.to_s)
96
- op_name, param_types = @operations[method.to_s]
90
+ method_in_snake_case = method.to_s.snake_case # this way Java/JRuby styles are compatible
91
+
92
+ if @operations.keys.include?(method_in_snake_case)
93
+ op_name, param_types = @operations[method_in_snake_case]
97
94
  @connection.invoke @object_name,
98
- op_name,
99
- args.to_java(:Object),
100
- param_types.to_java(:String)
95
+ op_name,
96
+ args.to_java(:Object),
97
+ param_types.to_java(:String)
101
98
  else
102
99
  super
103
100
  end
@@ -115,6 +112,7 @@ module JMX
115
112
  #
116
113
  # JMX::MBean.establish_connection :port => "node23", :port => 1090
117
114
  # JMX::MBean.establish_connection :port => "node23", :username => "jeff", :password => "secret"
115
+ # JMX::MBean.establish_connection :command => /jconsole/i
118
116
  def self.establish_connection(args={})
119
117
  @@connection ||= create_connection args
120
118
  end
@@ -147,6 +145,16 @@ module JMX
147
145
  # if the url is specified, the host & port parameters are
148
146
  # not taken into account
149
147
  #
148
+ # [:command] the pattern matches the command line of the local
149
+ # JVM process including the MBean server.
150
+ # (command lines are listed on the connection dialog
151
+ # in JConsole).
152
+ # No default.
153
+ # this feature needs a JDK (>=5) installed on the local
154
+ # system.
155
+ # if the command is specified, the host & port or the url
156
+ # parameters are not taken into account
157
+ #
150
158
  # [:username] the name of the user (if the MBean server requires authentication).
151
159
  # No default
152
160
  #
@@ -168,9 +176,14 @@ module JMX
168
176
  credentials = args[:credentials]
169
177
  provider_package = args[:provider_package]
170
178
 
171
- # host & port are not taken into account if url is set (see issue #7)
172
- standard_url = "service:jmx:rmi:///jndi/rmi://#{host}:#{port}/jmxrmi"
173
- url = args[:url] || standard_url
179
+ if args[:command]
180
+ url = JDKHelper.find_local_url(args[:command]) or
181
+ raise "no locally attacheable VMs"
182
+ else
183
+ # host & port are not taken into account if url is set (see issue #7)
184
+ standard_url = "service:jmx:rmi:///jndi/rmi://#{host}:#{port}/jmxrmi"
185
+ url = args[:url] || standard_url
186
+ end
174
187
 
175
188
  unless credentials
176
189
  if !username.nil? and username.length > 0
@@ -219,7 +232,7 @@ module JMX
219
232
  object_name = ObjectName.new(name)
220
233
  connection = args[:connection] || MBean.connection(args)
221
234
  object_names = connection.queryNames(object_name, nil)
222
- object_names.map { |on| MBean.new(on, connection) }
235
+ object_names.map { |on| create_mbean on, connection }
223
236
  end
224
237
 
225
238
  # Same as #find_all_by_name but the ObjectName passed in parameter
@@ -227,7 +240,25 @@ module JMX
227
240
  # Only one single MBean is returned.
228
241
  def self.find_by_name(name, args={})
229
242
  connection = args[:connection] || MBean.connection(args)
230
- MBean.new ObjectName.new(name), connection
243
+ create_mbean ObjectName.new(name), connection
244
+ end
245
+
246
+ def self.create_mbean(object_name, connection)
247
+ info = connection.getMBeanInfo object_name
248
+ mbean = MBean.new object_name, connection
249
+ # define attribute accessor methods for the mbean
250
+ info.attributes.each do |mbean_attr|
251
+ mbean.meta_def mbean_attr.name.snake_case do
252
+ connection.getAttribute object_name, mbean_attr.name
253
+ end
254
+ if mbean_attr.isWritable
255
+ mbean.meta_def "#{mbean_attr.name.snake_case}=" do |value|
256
+ attribute = Attribute.new mbean_attr.name, value
257
+ connection.setAttribute object_name, attribute
258
+ end
259
+ end
260
+ end
261
+ mbean
231
262
  end
232
263
 
233
264
  def self.pretty_print (object_name, args={})
@@ -34,4 +34,11 @@ class TestAttribute < Test::Unit::TestCase
34
34
  def test_non_writable_attribute
35
35
  assert_raise(NoMethodError) { @memory.object_pending_finalization_count = -1 }
36
36
  end
37
+
38
+ def test_non_overlapping_attributes
39
+ assert_raise(NoMethodError) { @memory.logger_names }
40
+ logging = JMX::MBean.find_by_name "java.util.logging:type=Logging", :connection => ManagementFactory.platform_mbean_server
41
+ assert_raise(NoMethodError) { logging.verbose }
42
+ assert_raise(NoMethodError) { @memory.logger_names }
43
+ end
37
44
  end
@@ -77,4 +77,15 @@ class TestConnection < Test::Unit::TestCase
77
77
  end
78
78
  end
79
79
 
80
+ def test_establish_connection_local
81
+ begin
82
+ JConsole::start :port => 0
83
+ connection = JMX::MBean.establish_connection \
84
+ :command => /jconsole/i
85
+ assert(connection.getMBeanCount > 0)
86
+ ensure
87
+ JConsole::stop 0
88
+ end
89
+ end
90
+
80
91
  end
@@ -76,4 +76,53 @@ class TestDynamicMBean < Test::Unit::TestCase
76
76
  mbean = JMX::MBean.find_by_name "jmx4r:name=OperationInvocationMBean", :connection => mbeanServer
77
77
  assert_equal("oof", mbean.reverse("foo"))
78
78
  end
79
+
80
+ class Foo < JMX::DynamicMBean
81
+ rw_attribute :foo_attr, :string
82
+
83
+ operation
84
+ parameter :string
85
+ returns :string
86
+ def foo(arg)
87
+ "foo #{arg}"
88
+ end
89
+ end
90
+
91
+ class Bar < JMX::DynamicMBean
92
+ rw_attribute :bar_attr, :string
93
+
94
+ operation
95
+ parameter :string
96
+ returns :string
97
+ def bar(arg)
98
+ "bar #{arg}"
99
+ end
100
+ end
101
+
102
+ def test_separate_dynamic_beans_have_separate_operations_and_attributes
103
+ mbean_server = ManagementFactory.platform_mbean_server
104
+ mbean_server.register_mbean Foo.new, ObjectName.new("jmx4r:name=foo")
105
+ mbean_server.register_mbean Bar.new, ObjectName.new("jmx4r:name=bar")
106
+
107
+ foo_mbean = JMX::MBean.find_by_name "jmx4r:name=foo", :connection => mbean_server
108
+ assert_equal "foo test", foo_mbean.foo("test")
109
+ assert_raise(NoMethodError){
110
+ foo_mbean.bar("test")
111
+ }
112
+ foo_mbean.foo_attr = "test"
113
+ assert_equal "test", foo_mbean.foo_attr
114
+ assert_raise(NoMethodError){
115
+ foo_mbean.bar_attr = "test"
116
+ }
117
+ bar_mbean = JMX::MBean.find_by_name "jmx4r:name=bar", :connection => mbean_server
118
+ assert_equal "bar test", bar_mbean.bar("test")
119
+ assert_raise(NoMethodError) {
120
+ bar_mbean.foo("test")
121
+ }
122
+ bar_mbean.bar_attr = "test"
123
+ assert_equal "test", bar_mbean.bar_attr
124
+ assert_raise(NoMethodError){
125
+ bar_mbean.foo_attr = "test"
126
+ }
127
+ end
79
128
  end
@@ -0,0 +1,30 @@
1
+ # Copyright 2007 Jeff Mesnil (http://jmesnil.net)
2
+
3
+ require "test/unit"
4
+
5
+ require "jmx4r"
6
+ require "jconsole"
7
+
8
+ class TestMethods < Test::Unit::TestCase
9
+ java_import java.lang.management.ManagementFactory
10
+
11
+ def setup
12
+ @logging = JMX::MBean.find_by_name "java.util.logging:type=Logging", :connection => ManagementFactory.platform_mbean_server
13
+ end
14
+
15
+ def teardown
16
+ JMX::MBean.remove_connection
17
+ end
18
+
19
+ def test_invoke_operation
20
+ @logging.set_logger_level "global", "FINEST"
21
+ assert_equal "FINEST", @logging.get_logger_level("global")
22
+ end
23
+
24
+ # make sure we can also use Java name convention
25
+ def test_invoke_CamelCaseOperation
26
+ @logging.setLoggerLevel "global", "FINE"
27
+ assert_equal "FINE", @logging.getLoggerLevel("global")
28
+ end
29
+
30
+ end
data/test/ts_all.rb CHANGED
@@ -6,6 +6,7 @@ require "tc_connection"
6
6
  require "tc_auth"
7
7
  require "tc_multiple_connections"
8
8
  require "tc_attributes"
9
+ require "tc_methods"
9
10
  require "tc_composite_data"
10
11
  require "tc_dynamic_mbean"
11
12
 
metadata CHANGED
@@ -42,14 +42,19 @@ files:
42
42
  - examples/runtime_sysprops.rb
43
43
  - lib/dynamic_mbean.rb
44
44
  - lib/jconsole.rb
45
+ - lib/jdk_helper.rb
45
46
  - lib/jmx4r.rb
46
47
  - lib/objectname_helper.rb
47
48
  - lib/open_data_helper.rb
49
+ - lib/jdk/jdk4.rb
50
+ - lib/jdk/jdk5.rb
51
+ - lib/jdk/jdk6.rb
48
52
  - test/tc_attributes.rb
49
53
  - test/tc_auth.rb
50
54
  - test/tc_composite_data.rb
51
55
  - test/tc_connection.rb
52
56
  - test/tc_dynamic_mbean.rb
57
+ - test/tc_methods.rb
53
58
  - test/tc_multiple_connections.rb
54
59
  - test/ts_all.rb
55
60
  - Rakefile
@@ -68,12 +73,12 @@ requirements: []
68
73
 
69
74
  authors:
70
75
  - Jeff Mesnil
71
- date: 2009-06-14 22:00:00 +00:00
76
+ date: 2009-10-21 22:00:00 +00:00
72
77
  platform: ruby
73
78
  test_files:
74
79
  - test/ts_all.rb
75
80
  version: !ruby/object:Gem::Version
76
- version: 0.0.8
81
+ version: 0.1.0
77
82
  require_paths:
78
83
  - lib
79
84
  dependencies: []