table_setter 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. data/.document +5 -0
  2. data/.gitignore +20 -0
  3. data/LICENSE +20 -0
  4. data/README +10 -0
  5. data/Rakefile +66 -0
  6. data/TODO +3 -0
  7. data/VERSION.yml +5 -0
  8. data/bin/table-setter +5 -0
  9. data/doc/TableFu/Formatting.html +178 -0
  10. data/doc/TableSetter/App.html +176 -0
  11. data/doc/TableSetter/Command.html +376 -0
  12. data/doc/TableSetter/Table.html +1813 -0
  13. data/doc/TableSetter.html +292 -0
  14. data/doc/_index.html +146 -0
  15. data/doc/class_list.html +36 -0
  16. data/doc/css/common.css +1 -0
  17. data/doc/css/full_list.css +50 -0
  18. data/doc/css/style.css +268 -0
  19. data/doc/file.README.html +68 -0
  20. data/doc/file_list.html +38 -0
  21. data/doc/frames.html +13 -0
  22. data/doc/index.html +68 -0
  23. data/doc/js/app.js +99 -0
  24. data/doc/js/full_list.js +106 -0
  25. data/doc/js/jquery.js +19 -0
  26. data/doc/method_list.html +291 -0
  27. data/doc/top-level-namespace.html +85 -0
  28. data/documentation/css/dawn.css +121 -0
  29. data/documentation/css/styles.css +63 -0
  30. data/documentation/images/folder.png +0 -0
  31. data/documentation/images/key.png +0 -0
  32. data/documentation/images/proplogo.png +0 -0
  33. data/documentation/images/publish.png +0 -0
  34. data/documentation/images/text-x-generic.png +0 -0
  35. data/documentation/index.html.erb +221 -0
  36. data/documentation/tables/example/index.html +4074 -0
  37. data/documentation/tables/example_faceted/index.html +17239 -0
  38. data/documentation/tables/example_formatted/index.html +861 -0
  39. data/documentation/tables/example_local/1/index.html +6084 -0
  40. data/documentation/tables/example_local/10/index.html +6084 -0
  41. data/documentation/tables/example_local/11/index.html +6084 -0
  42. data/documentation/tables/example_local/12/index.html +6084 -0
  43. data/documentation/tables/example_local/13/index.html +6084 -0
  44. data/documentation/tables/example_local/14/index.html +6084 -0
  45. data/documentation/tables/example_local/15/index.html +6084 -0
  46. data/documentation/tables/example_local/16/index.html +6084 -0
  47. data/documentation/tables/example_local/17/index.html +6084 -0
  48. data/documentation/tables/example_local/18/index.html +6084 -0
  49. data/documentation/tables/example_local/19/index.html +6084 -0
  50. data/documentation/tables/example_local/2/index.html +6084 -0
  51. data/documentation/tables/example_local/20/index.html +6084 -0
  52. data/documentation/tables/example_local/21/index.html +6084 -0
  53. data/documentation/tables/example_local/22/index.html +6084 -0
  54. data/documentation/tables/example_local/23/index.html +6084 -0
  55. data/documentation/tables/example_local/24/index.html +1404 -0
  56. data/documentation/tables/example_local/3/index.html +6084 -0
  57. data/documentation/tables/example_local/4/index.html +6084 -0
  58. data/documentation/tables/example_local/5/index.html +6084 -0
  59. data/documentation/tables/example_local/6/index.html +6084 -0
  60. data/documentation/tables/example_local/7/index.html +6084 -0
  61. data/documentation/tables/example_local/8/index.html +6084 -0
  62. data/documentation/tables/example_local/9/index.html +6084 -0
  63. data/documentation/tables/example_local/index.html +6084 -0
  64. data/documentation/tables/favicon.ico +0 -0
  65. data/documentation/tables/images/th_arrow_asc.gif +0 -0
  66. data/documentation/tables/images/th_arrow_desc.gif +0 -0
  67. data/documentation/tables/index.html +48 -0
  68. data/documentation/tables/javascripts/application.js +32 -0
  69. data/documentation/tables/javascripts/jquery.tablesorter.js +852 -0
  70. data/documentation/tables/javascripts/jquery.tablesorter.multipagefilter.js +111 -0
  71. data/documentation/tables/javascripts/jquery.tablesorter.pager.js +183 -0
  72. data/documentation/tables/stylesheets/stylesheet.css +67 -0
  73. data/index.html +238 -0
  74. data/lib/table_setter/app.rb +54 -0
  75. data/lib/table_setter/command.rb +139 -0
  76. data/lib/table_setter/table.rb +226 -0
  77. data/lib/table_setter.rb +32 -0
  78. data/spec/spec.opts +1 -0
  79. data/spec/spec_helper.rb +11 -0
  80. data/spec/table-setter-app_spec.rb +24 -0
  81. data/spec/table-setter_spec.rb +179 -0
  82. data/table_setter.gemspec +171 -0
  83. data/template/config.ru +33 -0
  84. data/template/public/favicon.ico +0 -0
  85. data/template/public/images/th_arrow_asc.gif +0 -0
  86. data/template/public/images/th_arrow_desc.gif +0 -0
  87. data/template/public/javascripts/application.js +32 -0
  88. data/template/public/javascripts/jquery.tablesorter.js +852 -0
  89. data/template/public/javascripts/jquery.tablesorter.multipagefilter.js +111 -0
  90. data/template/public/javascripts/jquery.tablesorter.pager.js +183 -0
  91. data/template/public/stylesheets/stylesheet.css +74 -0
  92. data/template/tables/example.yml +21 -0
  93. data/template/tables/example_faceted.yml +27 -0
  94. data/template/tables/example_formatted.csv +1 -0
  95. data/template/tables/example_formatted.yml +27 -0
  96. data/template/tables/example_local.csv +5806 -0
  97. data/template/tables/example_local.yml +27 -0
  98. data/template/views/404.erb +4 -0
  99. data/template/views/500.erb +4 -0
  100. data/template/views/index.erb +7 -0
  101. data/template/views/layout.erb +34 -0
  102. data/template/views/table.erb +78 -0
  103. metadata +240 -0
@@ -0,0 +1,139 @@
1
+ require 'optparse'
2
+ require 'rack'
3
+ require 'rack/showexceptions'
4
+ require 'rack/commonlogger'
5
+ require 'rack/lint'
6
+
7
+ module TableSetter
8
+ class Command
9
+ BANNER = <<-EOB
10
+ table-setter is a Sinatra application for rendering and processing CSVs from google docs into HTML.
11
+
12
+ Usage:
13
+ table-setter COMMAND path/to/table-setter/assets OPTIONS
14
+
15
+ commands:
16
+ start run the development server, for deployment use config.ru
17
+ install copy the table-setter assets into the the directory
18
+ export statically build tables in the ./out/
19
+
20
+ options:
21
+ EOB
22
+
23
+
24
+ def initialize
25
+ @prefix = ""
26
+ parse_options
27
+ command = ARGV.shift
28
+ @directory = ARGV.shift || '.'
29
+ TableSetter.configure @directory
30
+ case command
31
+ when 'start' then start_server
32
+ when 'install' then install_assets
33
+ when 'build' then build_out
34
+ else puts BANNER
35
+ end
36
+ end
37
+
38
+ def start_server
39
+ app = build_rack
40
+ Rack::Handler::Thin.run app, :Port => "3000"
41
+ end
42
+
43
+ def install_assets
44
+ FileUtils.mkdir_p @directory unless File.exists? @directory
45
+ puts "\nInstalling TableSetter files...\n\n"
46
+ base_files.each do |path|
47
+ copy_file path, File.join(TableSetter.config_path, path.gsub(ROOT + "/template/", "/"))
48
+ end
49
+ end
50
+
51
+
52
+
53
+ def build_out
54
+ @out_dir = File.join(TableSetter.config_path, 'out', @prefix)
55
+ puts "\nBuilding your TableSetter files...\n\n"
56
+ app = build_rack
57
+ request = Rack::MockRequest.new(app)
58
+ build_index request
59
+ build_assets
60
+ build_tables request
61
+ end
62
+
63
+ private
64
+ # Option parsing
65
+ def parse_options
66
+ @options = {}
67
+ @option_parser = OptionParser.new do |opts|
68
+ opts.on "-p", "--prefix PREFIX", "url prefix for the export command" do |prefix|
69
+ @prefix = "#{prefix}"
70
+ end
71
+ end
72
+ @option_parser.banner = BANNER
73
+ @option_parser.parse! ARGV
74
+ end
75
+
76
+ def build_rack
77
+ prefix = @prefix
78
+ Rack::Builder.app do
79
+ map "/#{prefix}" do
80
+ use Rack::CommonLogger, STDERR
81
+ use Rack::ShowExceptions
82
+ use Rack::Lint
83
+ run TableSetter::App
84
+ end
85
+ end
86
+ end
87
+
88
+ def build_index(request)
89
+ install_file(request.request("GET", "/#{@prefix}/").body,
90
+ File.join(@out_dir, "index.html"))
91
+ end
92
+
93
+ def build_assets
94
+ Dir[ROOT + "/template/public/**/*"].each do |path|
95
+ copy_file path, File.join(path.gsub(ROOT + "/template/public/", "#{@out_dir}/"))
96
+ end
97
+ end
98
+
99
+ def build_tables(request)
100
+ TableSetter::Table.all.each do |table|
101
+ puts "Building #{table.slug}"
102
+ install_file(request.request("GET", "/#{@prefix}/#{table.slug}/").body,
103
+ File.join(@out_dir, table.slug, "index.html"))
104
+ if table.hard_paginate?
105
+ table.load
106
+ (1..table.total_pages).each do |page|
107
+ puts "Building #{table.slug} #{page} of #{table.total_pages}"
108
+ install_file(request.request("GET", "/#{@prefix}/#{table.slug}/#{page}/").body,
109
+ File.join(@out_dir, table.slug, page.to_s, "index.html"))
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ def base_files
116
+ Dir[ROOT + "/template/**/*"]
117
+ end
118
+
119
+ def copy_file(source, dest)
120
+ ensure_directory dest
121
+ exists = File.exists? dest
122
+ FileUtils.cp_r(source, dest) unless exists
123
+ puts "#{exists ? "exists" : "created"}\t#{dest}"
124
+ end
125
+
126
+ def ensure_directory(dest)
127
+ expanded_path = File.dirname dest
128
+ FileUtils.mkdir_p(expanded_path) unless File.exists?(expanded_path)
129
+ end
130
+
131
+ def install_file(body, dest)
132
+ ensure_directory dest
133
+ File.open(dest, "w") do |file|
134
+ file.write(body)
135
+ end
136
+ end
137
+
138
+ end
139
+ end
@@ -0,0 +1,226 @@
1
+ require 'curb'
2
+ require 'fastercsv'
3
+ require 'table_fu'
4
+ require 'net/http'
5
+
6
+ module TableSetter
7
+ class Table
8
+ # The +Table+ class handles processing the yaml processing and csv loading,
9
+ # through table fu
10
+ attr_reader :data, :table_opts, :facets, :prev_page, :next_page, :page
11
+
12
+ # A new Table should accept a slug, mapped to a yaml in the tables directory,
13
+ # optionally you can defer loading of the table until you're ready to render it.
14
+ def initialize(slug, opts={:defer => false})
15
+ options = indifferent_access YAML.load_file(Table.table_path(slug))
16
+ @table_opts = options[:table]
17
+ @table_opts[:slug] = slug
18
+ @deferred = opts[:defer]
19
+ if !@deferred
20
+ self.load
21
+ end
22
+ end
23
+
24
+ # The load method handles the actual request either to the file system or remote url.
25
+ # It performs the requested data manipulations form the yml file after the data has been loaded.
26
+ # We're keeping this explicit to control against unnecessary http requests.
27
+ def load
28
+ csv = csv_data
29
+ @data = TableFu.new(csv_data, @table_opts[:column_options] || {})
30
+ if @table_opts[:faceting]
31
+ @data.col_opts[:ignored] = [@table_opts[:faceting][:facet_by]]
32
+ @facets = @data.faceted_by @table_opts[:faceting][:facet_by]
33
+ end
34
+ @data.delete_rows! @table_opts[:dead_rows] if @table_opts[:dead_rows]
35
+ end
36
+
37
+ # The csv_data for the table fu instance is loaded either from the remote source or from a local
38
+ # file, depending on the keys present in the yaml file.
39
+ def csv_data
40
+ case
41
+ when google_key || url then Curl::Easy.perform(uri).body_str
42
+ when file then File.open(uri).read
43
+ end
44
+ end
45
+
46
+ # Returns a usable uri based on what sort of input we have.
47
+ def uri
48
+ case
49
+ when google_key then "http://spreadsheets.google.com/pub?key=#{google_key}&output=csv"
50
+ when url then url
51
+ when file then File.expand_path("#{TableSetter.table_path}#{file}")
52
+ end
53
+ end
54
+
55
+ # The real +updated_at+ of a Table instance is the newer modification time of the csv file or
56
+ # the yaml file. Updates to either resource should break the cache.
57
+ def updated_at
58
+ csv_time = google_key.nil? ? modification_time(uri) : google_modification_time
59
+ (csv_time > yaml_time ? csv_time : yaml_time).to_s
60
+ end
61
+
62
+ def faceted?
63
+ !@facets.nil?
64
+ end
65
+
66
+ # A table isn't sortable by tablesorter if it's either faceted or multi-page paginated.
67
+ def sortable?
68
+ !faceted? && !hard_paginate?
69
+ end
70
+
71
+ # hard_paginate instructs the app to render batches of a table.
72
+ def hard_paginate?
73
+ @table_opts[:hard_paginate] == true
74
+ end
75
+
76
+ # The number of rows per page. Defaults to 20
77
+ def per_page
78
+ @table_opts[:per_page] || 20
79
+ end
80
+
81
+ # paginate uses TableFu's only! method to batch the table. It also computes the page attributes
82
+ # which are nil and meaningless otherwise.
83
+ def paginate!(curr_page)
84
+ return if !hard_paginate?
85
+ @page = curr_page.to_i
86
+ raise ArgumentError if @page < 1 || @page > total_pages
87
+ adj_page = @page - 1 > 0 ? @page - 1 : 0
88
+ @prev_page = adj_page > 0 ? adj_page : nil
89
+ @next_page = page < total_pages ? (@page + 1) : nil
90
+ @data.only!(adj_page * per_page..(@page * per_page - 1))
91
+ end
92
+
93
+
94
+ # The total pages we'll have. We need to calculate it before paginate, so that we still have the
95
+ # full @data.rows.length
96
+ def total_pages
97
+ @total_pages ||= (@data.rows.length / per_page.to_f).ceil
98
+ end
99
+
100
+ # A convienence method to return the sort array for table setter.
101
+ def sort_array
102
+ @data.sorted_by.inject([]) do |memo, (key, value)|
103
+ memo << [@data.columns.index(key), value == 'descending' ? 0 : 1]
104
+ end
105
+ end
106
+
107
+ # We magically need access to the top level keys like google_key, or uri for the other methods.
108
+ # It's a bit dangerous because everything returns nil otherwise. At some point we should eval
109
+ # and create methods at boot time.
110
+ def method_missing(method)
111
+ if @table_opts[method]
112
+ @table_opts[method]
113
+ end
114
+ end
115
+
116
+ private
117
+
118
+ # Returns the google modification time of the spreadsheet. The public urls don't set the
119
+ # last-modified header on anything, so we have to do a little dance to find out when exactly
120
+ # the spreadsheet was last modified. The od[0-9] part of the feed url changes at whim, so we'll
121
+ # need to keep an eye on it. Another propblem is that curb doesn't feel like parsing headers, so
122
+ # since a head request from google is pretty lightweight we can get away with using Net:HTTP.
123
+ # If for whatever reason the google modification time is busted we'll return the epoch,
124
+ # and rely on the yaml modified time.
125
+ def google_modification_time
126
+ local_url = URI.parse "http://spreadsheets.google.com/feeds/list/#{google_key}/od7/public/basic"
127
+ web_modification_time local_url
128
+ end
129
+
130
+ # Returns the last-modified time from the remote server. Assumes the remote server knows how to
131
+ # do this. Returns the epoch if the remote is dense.
132
+ def web_modification_time(local_url)
133
+ resp = nil
134
+ Net::HTTP.start(local_url.host, 80) do |http|
135
+ resp = http.head(local_url.path)
136
+ end
137
+ resp['Last-Modified'].nil? ? Time.at(0) : Time.parse(resp['Last-Modified'])
138
+ end
139
+
140
+ # Dispatches to web_modification_time if we're dealing with a url, otherwise just stats the
141
+ # local file.
142
+ def modification_time(path)
143
+ is_uri = URI.parse(path)
144
+ if !is_uri.host.nil?
145
+ return web_modification_time is_uri
146
+ end
147
+ File.new(path).mtime
148
+ end
149
+
150
+ # The modification time of this Table's yaml file.
151
+ def yaml_time
152
+ modification_time(Table.table_path(slug))
153
+ end
154
+
155
+ # Enable string or symbol key access to col_opts
156
+ # from sinatra.
157
+ def indifferent_access(params)
158
+ params = indifferent_hash.merge(params)
159
+ params.each do |key, value|
160
+ next unless value.is_a?(Hash)
161
+ params[key] = indifferent_access(value)
162
+ end
163
+ end
164
+
165
+ # Duplicate a hash's keys and convert them into symbols.
166
+ def indifferent_hash
167
+ Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
168
+ end
169
+
170
+ public
171
+
172
+ class << self
173
+
174
+ # Returns all the tables in the table directory. Each table is deferred so accessing the @data
175
+ # attribute will throw and error.
176
+ def all
177
+ tables=[]
178
+ Dir.glob("#{TableSetter.table_path}/*.yml").each do |file|
179
+ table = new(File.basename(file, ".yml"), :defer => true)
180
+ tables << table if table.live
181
+ end
182
+ tables
183
+ end
184
+
185
+ # +fresh_yaml_time+ checks each file in the tables directory and returns the newest file's
186
+ # modification time -- there's probably a more unix-y way to do this but for now this is
187
+ # plenty speedy.
188
+ def fresh_yaml_time
189
+ newest_file = Dir["#{TableSetter.table_path}/*.yml"].inject do |memo, obj|
190
+ memo_time = File.new(File.expand_path memo).mtime
191
+ obj_time = File.new(File.expand_path obj).mtime
192
+ if memo_time > obj_time
193
+ memo
194
+ else
195
+ obj
196
+ end
197
+ end
198
+ File.new(newest_file).mtime
199
+ end
200
+
201
+ # Convenience method for looking up by slug.
202
+ def table_path(slug)
203
+ "#{TableSetter.table_path}#{slug}.yml"
204
+ end
205
+
206
+ # Does a table with this slug exist?
207
+ def exists?(slug)
208
+ File.exists? table_path(slug)
209
+ end
210
+ end
211
+
212
+ end
213
+ end
214
+
215
+ class TableFu::Formatting
216
+ class << self
217
+ # In order to show a sideways bar chart, we're extending the builtin TableFu formatters.
218
+ def bar(percent)
219
+ percent = percent.to_f
220
+ if percent < 1
221
+ percent = percent * 100
222
+ end
223
+ "<div class=\"bar\" style=\"width:#{percent}%\">#{percent}%</div>"
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,32 @@
1
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__))
2
+
3
+ require 'table_fu'
4
+ require 'yaml'
5
+
6
+ autoload :Sinatra, 'sinatra/base'
7
+ autoload :Thin, 'thin'
8
+ autoload :ERB, 'erb'
9
+ autoload :FasterCSV, 'FasterCSV'
10
+ autoload :Curb, 'curb'
11
+
12
+ module TableSetter
13
+ # autoload internals
14
+ autoload :App, 'table_setter/app'
15
+ autoload :Command, 'table_setter/command'
16
+ autoload :Table, 'table_setter/table'
17
+
18
+ ROOT = File.expand_path(File.dirname(__FILE__) + "/..") unless defined? ROOT
19
+
20
+ class << self
21
+ attr_reader :config_path
22
+
23
+ def configure(path)
24
+ @config_path = File.expand_path(path)
25
+ end
26
+
27
+ def table_path
28
+ @config_path + "/tables/"
29
+ end
30
+
31
+ end
32
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,11 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'rubygems'
4
+ require 'table_setter'
5
+ require 'spec'
6
+ require 'rack/test'
7
+ require 'spec/autorun'
8
+ TableSetter.configure(File.join(File.dirname(__FILE__), "..", "template"))
9
+ Spec::Runner.configure do |config|
10
+
11
+ end
@@ -0,0 +1,24 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+
4
+ describe TableSetter::App, "in the application" do
5
+ include Rack::Test::Methods
6
+
7
+ def app
8
+ TableSetter::App
9
+ end
10
+
11
+
12
+ it "should render the homepage" do
13
+ get '/'
14
+ last_response.body.include?("All Tables").should be_true
15
+ end
16
+
17
+
18
+ it "should render a table" do
19
+ get '/example/'
20
+ last_response.ok?.should be_true
21
+ last_response.body.include?("Failed Banks List").should be_true
22
+ end
23
+
24
+ end
@@ -0,0 +1,179 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+
4
+ describe TableSetter::Table do
5
+ it 'should return the latest yaml modification time' do
6
+ `touch #{TableSetter::Table.table_path('example')}`
7
+ TableSetter::Table.fresh_yaml_time.should eql(
8
+ File.new(TableSetter::Table.table_path('example')).mtime)
9
+ end
10
+ end
11
+
12
+ describe TableSetter::Table do
13
+ before :all do
14
+ @table = TableSetter::Table.new("example_local")
15
+ end
16
+
17
+ it "should load from a google key, and defer loading when asked" do
18
+ table = TableSetter::Table.new("example", :defer => true)
19
+ table.data.should be_nil
20
+ table.load
21
+ table.data.should_not be_nil
22
+ table.data.headers.should_not be_nil
23
+ end
24
+
25
+ it 'should be able to find out if a given table exists' do
26
+ TableSetter::Table.exists?("non_existent_table").should be_false
27
+ TableSetter::Table.exists?("example").should be_true
28
+ end
29
+
30
+ it "should have a slug" do
31
+ @table.slug.should eql "example_local"
32
+ end
33
+
34
+ it "should have a deck" do
35
+ @table.deck.should_not be_nil
36
+ end
37
+
38
+ it "should have 250 items per page" do
39
+ @table.per_page.should eql 250
40
+ end
41
+
42
+ it "should be sortable" do
43
+ @table.sortable?.should be_false
44
+ end
45
+
46
+ it "should have a footer" do
47
+ @table.footer.should_not be_nil
48
+ end
49
+
50
+ it "should have a title" do
51
+ @table.title.should_not be_nil
52
+ end
53
+
54
+ it "should have 5805 rows" do
55
+ @table.data.rows.length.should eql 5805
56
+ end
57
+
58
+ it "should be stylish" do
59
+ @table.data.rows[1].column_for('State').style.should eql 'text-align:left;'
60
+ @table.data.rows[1].column_for('Project Description').style.should eql 'text-align:right;'
61
+ end
62
+
63
+ it "should have stylish headers" do
64
+ @table.data.headers[0].style.should eql 'text-align:left;'
65
+ @table.data.headers[4].style.length.should eql 0
66
+ end
67
+
68
+ it "should be formatted" do
69
+ @table.data.rows[1].column_for('ARRA Funds Obligated').to_s.should eql '$154,446'
70
+ end
71
+
72
+ end
73
+
74
+
75
+ describe TableSetter::Table, "with hard pagination" do
76
+
77
+ before :each do
78
+ @data = TableSetter::Table.new("example_local")
79
+ end
80
+
81
+ it "should not be sortable" do
82
+ @data.sortable?.should eql false
83
+ end
84
+
85
+ it "should be paginated" do
86
+ @data.hard_paginate?.should eql true
87
+ end
88
+
89
+ it "should paginate based on a page" do
90
+ @data.paginate! 3
91
+ @data.page.should eql 3
92
+ @data.prev_page.should eql 2
93
+ @data.next_page.should eql 4
94
+ @data.data.rows.length.should eql @data.per_page
95
+ @data.data.rows[0].column_for('State').to_s.should eql 'CALIFORNIA'
96
+ end
97
+
98
+ it 'should not paginate when given a bad value' do
99
+ lambda {@data.paginate!(-1)}.should raise_exception(ArgumentError)
100
+ lambda {@data.paginate!(10000000)}.should raise_exception(ArgumentError)
101
+ end
102
+
103
+ it 'should handle first page' do
104
+ @data.paginate! 1
105
+ @data.page.should eql 1
106
+ @data.prev_page.should eql nil
107
+ end
108
+
109
+ it 'should handle last page' do
110
+ @data.paginate! @data.total_pages
111
+ @data.page.should eql @data.total_pages
112
+ @data.next_page.should eql nil
113
+ end
114
+
115
+ end
116
+
117
+
118
+ describe TableSetter::Table, "with faceting and macros" do
119
+
120
+ before :all do
121
+ @data = TableSetter::Table.new("example_faceted")
122
+ @tables = @data.facets
123
+ end
124
+
125
+ it 'should load a faceted_table' do
126
+ data = TableSetter::Table.new("example_faceted", :defer=> true)
127
+ data.facets.should be_nil
128
+ data.load
129
+ tables = data.facets
130
+ tables.length.should eql 56
131
+ end
132
+
133
+ it "should be faceted" do
134
+ @data.faceted?.should be_true
135
+ end
136
+
137
+ it "should not be sortable" do
138
+ @data.sortable?.should be_false
139
+ end
140
+
141
+
142
+ it "should have 3 tables" do
143
+ @tables.length.should eql 56
144
+ end
145
+
146
+ it "should have $212,774,529 for Alabama" do
147
+ @tables[0].total_for('Total Appropriation').to_s.should eql '$212,774,529'
148
+ end
149
+
150
+ it "should have $416,075,044 for North Carolina with dead row" do
151
+ @tables[35].total_for('Total Appropriation').to_s.should eql '$423,318,645'
152
+ end
153
+
154
+
155
+
156
+ end
157
+
158
+ describe TableSetter::Table, "group fetchers" do
159
+ it "should return live tables" do
160
+ TableSetter::Table.all.length.should eql 4
161
+ end
162
+ end
163
+
164
+ describe TableSetter::Table, "with urls and google bars" do
165
+ before :each do
166
+ @table = TableSetter::Table.new("example_formatted")
167
+ end
168
+
169
+ it "should have a link row" do
170
+ @table.data.rows[1].column_for('Agency Webpage').to_s.should eql "<a href='http://www.hhs.gov/recovery/' title='Health and Human Services'>Health and Human Services</a>"
171
+ end
172
+
173
+ it 'should show a bar' do
174
+ @table.data.rows[1].column_for('Spent (%)').to_s.should eql "<div class=\"bar\" style=\"width:42.0%\">42.0%</div>"
175
+ end
176
+ end
177
+
178
+
179
+