ruport 0.2.2 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
data/AUTHORS CHANGED
@@ -1 +1,7 @@
1
- {Gregory Brown}[mailto:gregory.t.brown@gmail.com]
1
+ {Gregory Brown}[mailto:gregory.t.brown@gmail.com]:
2
+
3
+ Original Author and Ruport::Report hacker.
4
+
5
+ {Robert Canieso}[mailto:rcanieso@gmail.com]:
6
+
7
+ Guy in charge of the Ruport::Format module
data/CHANGELOG CHANGED
@@ -1,4 +1,25 @@
1
- The current version of Ruby Reports is 0.2.2
1
+ The current version of Ruby Reports is 0.2.4
2
+
3
+ changes since Ruport 0.2.2:
4
+
5
+ - Report::DataSet and Report::DataRow are now enumerable
6
+
7
+ - DataSet#eql? fixed
8
+
9
+ - Format::Builder has been added to handle formatting
10
+ (Supports CSV / HTML currently)
11
+
12
+ - Format::Builder is easily extendable via send("render_#{@type}")
13
+
14
+ - DataSet#to_html convenience method added
15
+
16
+ - DataSet#fields can now be arbitrary objects (does not need to be Strings)
17
+
18
+ - Dropped DataRow#middle? because it was useless. (And poorly named)
19
+
20
+ - FakeMailer added for testing goodness.
21
+
22
+ - Dropped select() and execute() in favor of query()
2
23
 
3
24
  changes since Ruport 0.2.0:
4
25
 
data/README CHANGED
@@ -81,6 +81,9 @@ point, unless you're magic, you'll probably need to either read the
81
81
  {API Documentation}[http://ruport.rubyforge.org/docs/] or the source, whichever
82
82
  you're most comfortable with.
83
83
 
84
+ There is also a set of examples at:
85
+ http://rubyforge.org/frs/?group_id=856&release_id=3481
86
+
84
87
  I hope you enjoy this software and that it is useful to you.
85
88
 
86
89
  -Greg
data/Rakefile CHANGED
@@ -14,7 +14,7 @@ end
14
14
 
15
15
  spec = Gem::Specification.new do |spec|
16
16
  spec.name = "ruport"
17
- spec.version = "0.2.2"
17
+ spec.version = "0.2.4"
18
18
  spec.platform = Gem::Platform::RUBY
19
19
  spec.summary = "A generalized Ruby report generation and templating engine."
20
20
 
data/TODO CHANGED
@@ -1,15 +1,12 @@
1
1
  TODO:
2
2
 
3
3
  Bugfixes:
4
- - Fix potential failure in Ruport::Report::DataSet#eql?
5
- - Is there an OS X packaging problem with the gems?
6
4
  - Fix the manual that is in the example package so it it reads FakeDB instead
7
- of MockDB
5
+ of MockDB. Update the manual to cover new features in 0.2.4
8
6
 
9
7
  Improvement:
10
8
 
11
9
  - Mail system
12
- Hook up unit tests
13
10
  Make attachments doable
14
11
 
15
12
  - Template Config / Line Editing
@@ -27,6 +24,7 @@ Improvement:
27
24
  Expand so that it covers most common SQL commands.
28
25
  Form better more complete unit tests.
29
26
 
27
+ - Queries from file / db need to use ERb or some other replacement ability
30
28
 
31
29
  Begin implementing new features:
32
30
 
@@ -120,5 +118,6 @@ Other:
120
118
  This is only the tip of the iceburg. Please feel free to continue to fill my
121
119
  plate by sending any suggestions to gregory.t.brown@gmail.com
122
120
 
123
-
124
-
121
+ JEG2 Code Review 2005.11.14: (email me if you find any of this interesting)
122
+ - use the many levels of logger
123
+ - RQL (Ruport Query Language)
data/bin/ruport CHANGED
@@ -65,7 +65,7 @@ if ARGV[0].eql?("generate")
65
65
  }
66
66
  exit
67
67
  elsif ARGV[0].eql?("-v")
68
- puts "Ruport Version 0.2.2 \nA ruby report generation system by Gregory " +
68
+ puts "Ruport Version 0.2.4 \nA ruby report generation system by Gregory " +
69
69
  "Brown.\nThis application is Free Software under the GPL/Ruby License. " +
70
70
  "\nAll praise and/or criticism can be directed to "+
71
71
  "gregory.t.brown@gmail.com"
@@ -0,0 +1,34 @@
1
+ module Ruport
2
+ module Format
3
+ class Builder
4
+
5
+ def initialize( data_set )
6
+ @data = data_set
7
+ @type = nil
8
+ end
9
+
10
+ attr_accessor :type
11
+
12
+ def render
13
+ send("render_#{@type}")
14
+ end
15
+
16
+ def render_csv
17
+ @data.inject(CSV.generate_line(@data.fields) + "\n") do |out,r|
18
+ out << CSV.generate_line(@data.fields.map { |f| r[f] }) + "\n"
19
+ end
20
+ end
21
+
22
+ def render_html
23
+ fields_html = "<tr>\n <td>#{@data.fields.join('</td><td>')}</td>\n </tr>"
24
+ @data.inject("<table>\n #{fields_html}"){|html,row|
25
+ html << row.inject(" <tr>\n "){ |row_html, field|
26
+ row_html << "<td>#{field}</td>"
27
+ } + "\n </tr>"
28
+ } + "\n</table>"
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+
@@ -11,6 +11,8 @@ module Ruport
11
11
  module Report
12
12
  class DataRow
13
13
 
14
+ include Enumerable
15
+
14
16
  attr_accessor :fields
15
17
 
16
18
  # DataRows are essentially arrays with named ordinal fields and
@@ -27,7 +29,7 @@ module Ruport
27
29
  #
28
30
  # i.e. row["phone"]
29
31
  def [](key)
30
- key.kind_of?(String) ? @data[@fields.index(key)] : @data[key]
32
+ key.kind_of?(Fixnum) ? @data[key] : @data[@fields.index(key)]
31
33
  end
32
34
 
33
35
  # Lets you set field values
@@ -53,13 +55,17 @@ module Ruport
53
55
  # DataRow#first? DataRow#last? DataRow#middle?
54
56
  # DataRow#odd? DataRow#even? which are all conditional methods.
55
57
  def method_missing(method)
56
- if %[last? first? middle?].include? method.to_s
58
+ if %[last? first? center?].include? method.to_s
57
59
  return @position.eql?(method.to_s[0..-2].to_sym)
58
60
  elsif %[odd? even?].include? method.to_s
59
61
  return @oddness.eql?(method.to_s[0..-2].to_sym)
60
62
  end
61
63
  super
62
64
  end
65
+
66
+ def each(&action)
67
+ @data.each(&action)
68
+ end
63
69
 
64
70
  attr_accessor :position
65
71
  end
@@ -9,6 +9,9 @@
9
9
  module Ruport
10
10
  module Report
11
11
  class DataSet
12
+
13
+ include Enumerable
14
+
12
15
  def initialize
13
16
  @data = []
14
17
  end
@@ -48,19 +51,21 @@ module Ruport
48
51
 
49
52
  oddness = (@data.length % 2 == 0 ? :even : :odd)
50
53
  position = (@data.length == 0 ? :first : :last)
51
- @data[@data.length - 1].position = :middle if @data.length > 1
54
+ @data[@data.length - 1].position = nil if @data.length > 1
52
55
  @data << DataRow.new(new_row,@fields,oddness,position)
53
56
  end
54
57
 
55
58
  # This works in best case scenario. It should return true if
56
59
  # both DataSets have the same values. Still working out the kinks here.
57
60
  def eql?(data2)
58
- return false unless (@data.length == data2.data.length)
59
- 0.upto(@data.length - 1) do |index|
60
- (@fields + data2.fields).uniq.each do |key|
61
- return false unless @data[index][key] == data2[index][key]
61
+ return false unless ( @data.length == data2.data.length and
62
+ @fields.eql?(data2.fields) )
63
+ @data.each_with_index do |row, r_index|
64
+ row.each_with_index do |field, f_index|
65
+ return false unless field.eql?(data2[r_index][f_index])
62
66
  end
63
67
  end
68
+
64
69
  return true
65
70
  end
66
71
 
@@ -82,21 +87,19 @@ module Ruport
82
87
 
83
88
  # Converts a DataSet to CSV
84
89
  def to_csv
85
- output = CSV.generate_line(@fields) + "\n"
86
- @data.each do |row|
87
- output << CSV.generate_line(@fields.map { |f| row[f] }) + "\n"
88
- end
89
- return output
90
+ builder = Format::Builder.new(self)
91
+ builder.type = :csv
92
+ builder.render
93
+ end
94
+ # Converts a Dataset to Html
95
+ def to_html
96
+ builder = Format::Builder.new(self)
97
+ builder.type = :html
98
+ builder.render
90
99
  end
91
-
92
100
  # Works like a standard each iterator
93
101
  def each(&action)
94
- @data[0..-1].each(&action)
95
- end
96
-
97
- # Works like a standard each_with_index iterator
98
- def each_with_index(&action)
99
- @data[0..-1].each_with_index(&action)
102
+ @data.each(&action)
100
103
  end
101
104
 
102
105
  end
@@ -21,69 +21,25 @@ module Ruport
21
21
  module Report
22
22
  class Engine
23
23
  def initialize( dsn, user, password, mailer=nil )
24
+
25
+ File.exists?("log") or FileUtils.mkdir("log")
26
+ @logger = Logger.new("log/ruport.log")
27
+
24
28
  @dsn = dsn
25
29
  @user = user
26
30
  @password = password
27
- @report_name = ""
28
31
  @mailer = mailer
29
- File.exists?("log") or FileUtils.mkdir("log")
30
- @logger = Logger.new("log/ruport.log")
31
32
  @generate = true
33
+ @report_name = ""
32
34
  @report = ""
35
+ @query_table = nil
36
+ @file = nil
37
+ @config = nil
33
38
  end
34
39
 
35
40
  attr_accessor :query_table, :file
36
41
  attr_reader :mailer, :generate, :config, :logger
37
42
 
38
- # * DEPRECATED: Use query() *
39
- #
40
- # Takes a query, an optional sourcetype, and a block which is passed the
41
- # results row by row. When passed a query in string form, it adds the
42
- # SELECT clause to the string and executes the query. When passed a
43
- # filename and a source :file, it looks in queries/ for the file specified.
44
- # When given a database query label, it looks in config[query_table] for a
45
- # query with the label specified. If no source is specified, it uses
46
- # string by default for the source.
47
- #
48
- # Example:
49
- #
50
- # select ( "* FROM test" )
51
- # Passes "SELECT * FROM test" to the database
52
- #
53
- # select ( "test.sql", :file )
54
- # Passes the contents of queries/test.sql to the database
55
- #
56
- # select ( "TEST", :db )
57
- # Calls the query TEST stored in the database and query_table specified in
58
- # config/ruport.yaml
59
-
60
- def select( query, source = :string, &action )
61
- source != :string || query = "SELECT " + query
62
- execute( query, source ) do |sth|
63
- @column_names = sth.column_names
64
- @first_row = true
65
- sth.fetch do |row|
66
- action.call(row) if block_given?
67
- @first_row = false
68
- end
69
- end
70
- end
71
-
72
- # * DEPRECATED, Use query() *
73
- #
74
- # Takes a query and an optional sourcetype and then runs the query
75
- # against the database. The output is not returned. This is useful for
76
- # doing construction and destruction actions.
77
- def execute( query, source = :string )
78
- query = get_query(source, query)
79
- DBI.connect(@dsn, @user, @password) do |dbh|
80
- dbh.prepare(query) do |sth|
81
- sth.execute()
82
- yield(sth)
83
- end
84
- end
85
- end
86
-
87
43
  # Takes a query and an optional sourcetype and then runs the query
88
44
  # against the database. The return from the query is then converted
89
45
  # into a DataSet which can then be manipulated.
@@ -159,8 +115,10 @@ module Ruport
159
115
  when :file
160
116
  load_file( query )
161
117
  when :db
162
- select ( "query FROM #{@query_table} WHERE " +
163
- "label LIKE '#{query}';" ) do |row| return row["query"] end
118
+ query ( "SELECT query FROM #{@query_table} WHERE " +
119
+ "label LIKE '#{query}';" ) do |data|
120
+ return data[0]["query"]
121
+ end
164
122
  end
165
123
  end
166
124
 
@@ -13,32 +13,10 @@ module Ruport
13
13
 
14
14
  attr_accessor :fake_db
15
15
 
16
- def execute (query, source = :string)
17
- query = get_query(source, query)
18
- yield(@fake_db.process(@dsn,@user,@password, query))
19
- end
20
-
21
- def select( query, source = :string, &action )
22
- source != :string || query = "SELECT " + query
23
- execute( query, source ) do |table|
24
- if table.kind_of?(DataSet)
25
- @column_names = table.fields
26
- else
27
- @column_names = table[0].keys
28
- end
29
- @first_row = true
30
- table.each do |row|
31
- row = row.to_hash if table.kind_of?(DataSet)
32
- action.call(row) if block_given?
33
- @first_row = false
34
- end
35
- end
36
- end
37
16
 
38
17
  def query ( query, source = :string, &action)
39
- execute( query, source ) do |table|
40
- action.call(table)
41
- end
18
+ query = get_query(source, query)
19
+ yield(@fake_db.process(@dsn,@user,@password, query))
42
20
  end
43
21
 
44
22
  end
@@ -0,0 +1,23 @@
1
+ module Ruport
2
+ module Report
3
+ class FakeMailer < Mailer
4
+
5
+ @@fake_server = Hash.new
6
+ @@fake_server[:port] = 25
7
+
8
+ def self.[]=(key,value)
9
+ @@fake_server[key] = value
10
+ end
11
+
12
+ def send_report(report_name="No Subject")
13
+ raise "Invalid Host" unless @@fake_server[:host].eql?(@host)
14
+ raise "Invalid Account" unless @@fake_server[:account].eql?(@account)
15
+ raise "Bad Email Address" unless @@fake_server[:address].eql?(@address)
16
+ raise "Bad Password" unless @@fake_server[:password].eql?(@password)
17
+ raise "Invalid Port" unless @@fake_server[:port].eql?(@port)
18
+ raise "Bad Authorization Type" unless @@fake_server[:auth].eql?(@auth)
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -5,4 +5,6 @@ require "ruport/report/fake_db"
5
5
  require "ruport/report/fake_engine"
6
6
  require "ruport/report/data_set"
7
7
  require "ruport/report/data_row"
8
+ require "ruport/report/fake_mailer"
9
+ require "ruport/format/builder"
8
10
  include Ruport
@@ -0,0 +1,25 @@
1
+
2
+ require "test/unit"
3
+ require "lib/ruportlib"
4
+ class TestBuilder < Test::Unit::TestCase
5
+ def setup
6
+ @data = Report::DataSet.new
7
+ @data.fields = %w[ col1 col2 col3 ]
8
+ @data.default = ""; @data << %w[ a b c ]; @data << %w[ d e f ]
9
+ @builder = Format::Builder.new( @data )
10
+ end
11
+
12
+ def test_render_html
13
+ @builder.type = :html
14
+ assert_equal("<table>\n <tr>\n <td>col1</td><td>col2</td>" +
15
+ "<td>col3</td>\n </tr>" +
16
+ " <tr>\n <td>a</td><td>b</td><td>c</td>\n </tr>" +
17
+ " <tr>\n <td>d</td><td>e</td><td>f</td>\n </tr>" +
18
+ "\n</table>", @builder.render )
19
+ end
20
+
21
+ def test_render_csv
22
+ @builder.type = :csv
23
+ assert_equal("col1,col2,col3\na,b,c\nd,e,f\n", @builder.render)
24
+ end
25
+ end
@@ -11,6 +11,7 @@ class TestDataRow < Test::Unit::TestCase
11
11
  @rows << [ 3 , 4 ]
12
12
  @rows << [ 5 , 6 ]
13
13
  @rows << { "foo" => 7, "bar" => 8 }
14
+ @rows << [ 9, 10 ]
14
15
  end
15
16
 
16
17
  def test_first?
@@ -20,14 +21,6 @@ class TestDataRow < Test::Unit::TestCase
20
21
  end
21
22
  end
22
23
 
23
- def test_middle?
24
- assert(!@rows[0].middle?)
25
- assert(!@rows[-1].middle?)
26
- @rows[1..-2].each do |row|
27
- assert(row.middle?)
28
- end
29
- end
30
-
31
24
  def test_last?
32
25
  @rows[0..-2].each do |row|
33
26
  assert(!row.last?)
@@ -54,8 +54,12 @@ class TestDataSet < Test::Unit::TestCase
54
54
  end
55
55
 
56
56
  def test_load
57
- loaded_data = Report::DataSet.load("test/data.csv")
58
- assert(@data.eql?(loaded_data))
57
+ loaded_data = Report::DataSet.load("test/data.csv")
58
+ @data.each_with_index do |row,r_index|
59
+ row.each_with_index do |field,f_index|
60
+ assert_equal(field,loaded_data[r_index][f_index])
61
+ end
62
+ end
59
63
  end
60
64
 
61
65
  def test_to_csv
@@ -64,4 +68,12 @@ class TestDataSet < Test::Unit::TestCase
64
68
  assert_equal("col1,col2,col3\na,b,c\nd,\"\",e\n",csv)
65
69
  end
66
70
 
71
+ def test_to_html
72
+ assert_equal("<table>\n <tr>\n <td>col1</td><td>col2</td>" +
73
+ "<td>col3</td>\n </tr>" +
74
+ " <tr>\n <td>a</td><td>b</td><td>c</td>\n </tr>" +
75
+ " <tr>\n <td>d</td><td></td><td>e</td>\n </tr>" +
76
+ "\n</table>", @data.to_html )
77
+ end
78
+
67
79
  end
@@ -5,27 +5,28 @@
5
5
 
6
6
  require "test/unit"
7
7
  require "lib/ruportlib"
8
- class TestRuport < Test::Unit::TestCase
8
+ class TestEngine < Test::Unit::TestCase
9
9
 
10
- def setup
11
- @report = Report::FakeEngine.new( "DBI:mysql:ruport:localhost", "test", "123")
12
- @report.query_table = "ruport_queries"
10
+ def setup
11
+ @report = Report::FakeEngine.new( "DBI:mysql:ruport:localhost",
12
+ "test", "123")
13
+ @report.query_table = "ruport_queries"
13
14
 
14
- @report.fake_db = Report::FakeDB.new([ "DBI:mysql","ruport",
15
- "localhost", "test", "123"])
15
+ @report.fake_db = Report::FakeDB.new([ "DBI:mysql","ruport",
16
+ "localhost", "test", "123"])
16
17
 
17
- @report.fake_db["SELECT * FROM ruport_test"] = @data =
18
- [ { "a" => "a column, row 1", "b" => "b column, row 1",
19
- "c" => "c column, row 1", "d" => "d column, row 1" },
20
- { "a" => "a column, row 2", "b" => "b column, row 2",
21
- "c" => "c column, row 2", "d" => "d column, row 2" },
22
- { "a" => "a column, row 3", "b" => "b column, row 3",
23
- "c" => "c column, row 3", "d" => "d column, row 3" } ]
24
-
25
- @report.fake_db[ "SELECT query FROM #{@report.query_table} " +
26
- "WHERE label LIKE 'sql_stored_test';"] =
27
- [ "query" => "SELECT * FROM ruport_test" ]
28
- end
18
+ @report.fake_db["SELECT * FROM ruport_test"] = @data =
19
+ [ { "a" => "a column, row 1", "b" => "b column, row 1",
20
+ "c" => "c column, row 1", "d" => "d column, row 1" },
21
+ { "a" => "a column, row 2", "b" => "b column, row 2",
22
+ "c" => "c column, row 2", "d" => "d column, row 2" },
23
+ { "a" => "a column, row 3", "b" => "b column, row 3",
24
+ "c" => "c column, row 3", "d" => "d column, row 3" } ]
25
+
26
+ @report.fake_db[ "SELECT query FROM #{@report.query_table} " +
27
+ "WHERE label LIKE 'sql_stored_test';"] =
28
+ [ "query" => "SELECT * FROM ruport_test" ]
29
+ end
29
30
 
30
31
  def test_load_file
31
32
  contents = "SELECT * FROM ruport_test"
@@ -62,36 +63,15 @@ end
62
63
  end
63
64
  end
64
65
 
65
- def test_sql
66
- row_i = 0
67
- @report.select("* FROM ruport_test") do |row|
68
- assert_equal(@data[row_i]["a"], row["a"].to_s)
69
- assert_equal(@data[row_i]["b"], row["b"].to_s)
70
- assert_equal(@data[row_i]["c"], row["c"].to_s)
71
- assert_equal(@data[row_i]["d"], row["d"].to_s)
72
- row_i += 1
73
- end
74
- end
75
-
76
66
  def test_sql_file
77
- row_i = 0
78
- @report.select("test/test.sql",:file) do |row|
79
- assert_equal(@data[row_i]["a"], row["a"].to_s)
80
- assert_equal(@data[row_i]["b"], row["b"].to_s)
81
- assert_equal(@data[row_i]["c"], row["c"].to_s)
82
- assert_equal(@data[row_i]["d"], row["d"].to_s)
83
- row_i += 1
67
+ @report.query("test/test.sql",:file) do |result|
68
+ assert_equal(@data,result)
84
69
  end
85
70
  end
86
71
 
87
72
  def test_sql_stored
88
- row_i = 0
89
- @report.select("sql_stored_test",:db) do |row|
90
- assert_equal(@data[row_i]["a"], row["a"].to_s)
91
- assert_equal(@data[row_i]["b"], row["b"].to_s)
92
- assert_equal(@data[row_i]["c"], row["c"].to_s)
93
- assert_equal(@data[row_i]["d"], row["d"].to_s)
94
- row_i += 1
73
+ @report.query("sql_stored_test",:db) do |result|
74
+ assert_equal(@data,result)
95
75
  end
96
76
  end
97
77
 
@@ -0,0 +1,21 @@
1
+ require "test/unit"
2
+ require "ruportlib"
3
+
4
+ class TestMailer < Test::Unit::TestCase
5
+
6
+ def setup
7
+ Report::FakeMailer[:host] = "mail.fakeruport.org"
8
+ Report::FakeMailer[:address] = "greg@fakeruport.org"
9
+ Report::FakeMailer[:account] = "greg"
10
+ Report::FakeMailer[:password] = "bubbles"
11
+ Report::FakeMailer[:auth] = :login
12
+
13
+ @mailer = Report::FakeMailer.new( "mail.fakeruport.org",
14
+ "greg@fakeruport.org",
15
+ "greg","bubbles", 25, :login )
16
+ end
17
+ def test_send
18
+ assert_nothing_raised { @mailer.send_report }
19
+ end
20
+
21
+ end
@@ -2,3 +2,5 @@ require "test/unit"
2
2
  require "test/tc_engine"
3
3
  require "test/tc_data_set"
4
4
  require "test/tc_data_row"
5
+ require "test/tc_builder"
6
+ require "test/tc_mailer"
metadata CHANGED
@@ -1,10 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.8.10
2
+ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: ruport
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.2.2
7
- date: 2005-11-11
6
+ version: 0.2.4
7
+ date: 2005-11-16 00:00:00 -06:00
8
8
  summary: A generalized Ruby report generation and templating engine.
9
9
  require_paths:
10
10
  - lib
@@ -24,21 +24,27 @@ required_ruby_version: !ruby/object:Gem::Version::Requirement
24
24
  version: 0.0.0
25
25
  version:
26
26
  platform: ruby
27
+ signing_key:
28
+ cert_chain:
27
29
  authors:
28
30
  - Gregory Brown
29
31
  files:
30
32
  - lib/ruportlib.rb
33
+ - lib/ruport/format/builder.rb
31
34
  - lib/ruport/format/chart.rb
32
35
  - lib/ruport/report/data_row.rb
33
36
  - lib/ruport/report/data_set.rb
34
37
  - lib/ruport/report/engine.rb
35
38
  - lib/ruport/report/fake_db.rb
36
39
  - lib/ruport/report/fake_engine.rb
40
+ - lib/ruport/report/fake_mailer.rb
37
41
  - lib/ruport/report/mailer.rb
38
42
  - lib/ruport/report/sql.rb
43
+ - test/tc_builder.rb
39
44
  - test/tc_data_row.rb
40
45
  - test/tc_data_set.rb
41
46
  - test/tc_engine.rb
47
+ - test/tc_mailer.rb
42
48
  - test/ts_all.rb
43
49
  - Rakefile
44
50
  - README