table_setter 0.1.2

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.
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
+