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.
- data/.document +5 -0
- data/.gitignore +20 -0
- data/LICENSE +20 -0
- data/README +10 -0
- data/Rakefile +66 -0
- data/TODO +3 -0
- data/VERSION.yml +5 -0
- data/bin/table-setter +5 -0
- data/doc/TableFu/Formatting.html +178 -0
- data/doc/TableSetter/App.html +176 -0
- data/doc/TableSetter/Command.html +376 -0
- data/doc/TableSetter/Table.html +1813 -0
- data/doc/TableSetter.html +292 -0
- data/doc/_index.html +146 -0
- data/doc/class_list.html +36 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +50 -0
- data/doc/css/style.css +268 -0
- data/doc/file.README.html +68 -0
- data/doc/file_list.html +38 -0
- data/doc/frames.html +13 -0
- data/doc/index.html +68 -0
- data/doc/js/app.js +99 -0
- data/doc/js/full_list.js +106 -0
- data/doc/js/jquery.js +19 -0
- data/doc/method_list.html +291 -0
- data/doc/top-level-namespace.html +85 -0
- data/documentation/css/dawn.css +121 -0
- data/documentation/css/styles.css +63 -0
- data/documentation/images/folder.png +0 -0
- data/documentation/images/key.png +0 -0
- data/documentation/images/proplogo.png +0 -0
- data/documentation/images/publish.png +0 -0
- data/documentation/images/text-x-generic.png +0 -0
- data/documentation/index.html.erb +221 -0
- data/documentation/tables/example/index.html +4074 -0
- data/documentation/tables/example_faceted/index.html +17239 -0
- data/documentation/tables/example_formatted/index.html +861 -0
- data/documentation/tables/example_local/1/index.html +6084 -0
- data/documentation/tables/example_local/10/index.html +6084 -0
- data/documentation/tables/example_local/11/index.html +6084 -0
- data/documentation/tables/example_local/12/index.html +6084 -0
- data/documentation/tables/example_local/13/index.html +6084 -0
- data/documentation/tables/example_local/14/index.html +6084 -0
- data/documentation/tables/example_local/15/index.html +6084 -0
- data/documentation/tables/example_local/16/index.html +6084 -0
- data/documentation/tables/example_local/17/index.html +6084 -0
- data/documentation/tables/example_local/18/index.html +6084 -0
- data/documentation/tables/example_local/19/index.html +6084 -0
- data/documentation/tables/example_local/2/index.html +6084 -0
- data/documentation/tables/example_local/20/index.html +6084 -0
- data/documentation/tables/example_local/21/index.html +6084 -0
- data/documentation/tables/example_local/22/index.html +6084 -0
- data/documentation/tables/example_local/23/index.html +6084 -0
- data/documentation/tables/example_local/24/index.html +1404 -0
- data/documentation/tables/example_local/3/index.html +6084 -0
- data/documentation/tables/example_local/4/index.html +6084 -0
- data/documentation/tables/example_local/5/index.html +6084 -0
- data/documentation/tables/example_local/6/index.html +6084 -0
- data/documentation/tables/example_local/7/index.html +6084 -0
- data/documentation/tables/example_local/8/index.html +6084 -0
- data/documentation/tables/example_local/9/index.html +6084 -0
- data/documentation/tables/example_local/index.html +6084 -0
- data/documentation/tables/favicon.ico +0 -0
- data/documentation/tables/images/th_arrow_asc.gif +0 -0
- data/documentation/tables/images/th_arrow_desc.gif +0 -0
- data/documentation/tables/index.html +48 -0
- data/documentation/tables/javascripts/application.js +32 -0
- data/documentation/tables/javascripts/jquery.tablesorter.js +852 -0
- data/documentation/tables/javascripts/jquery.tablesorter.multipagefilter.js +111 -0
- data/documentation/tables/javascripts/jquery.tablesorter.pager.js +183 -0
- data/documentation/tables/stylesheets/stylesheet.css +67 -0
- data/index.html +238 -0
- data/lib/table_setter/app.rb +54 -0
- data/lib/table_setter/command.rb +139 -0
- data/lib/table_setter/table.rb +226 -0
- data/lib/table_setter.rb +32 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/table-setter-app_spec.rb +24 -0
- data/spec/table-setter_spec.rb +179 -0
- data/table_setter.gemspec +171 -0
- data/template/config.ru +33 -0
- data/template/public/favicon.ico +0 -0
- data/template/public/images/th_arrow_asc.gif +0 -0
- data/template/public/images/th_arrow_desc.gif +0 -0
- data/template/public/javascripts/application.js +32 -0
- data/template/public/javascripts/jquery.tablesorter.js +852 -0
- data/template/public/javascripts/jquery.tablesorter.multipagefilter.js +111 -0
- data/template/public/javascripts/jquery.tablesorter.pager.js +183 -0
- data/template/public/stylesheets/stylesheet.css +74 -0
- data/template/tables/example.yml +21 -0
- data/template/tables/example_faceted.yml +27 -0
- data/template/tables/example_formatted.csv +1 -0
- data/template/tables/example_formatted.yml +27 -0
- data/template/tables/example_local.csv +5806 -0
- data/template/tables/example_local.yml +27 -0
- data/template/views/404.erb +4 -0
- data/template/views/500.erb +4 -0
- data/template/views/index.erb +7 -0
- data/template/views/layout.erb +34 -0
- data/template/views/table.erb +78 -0
- 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
|
data/lib/table_setter.rb
ADDED
|
@@ -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
|
data/spec/spec_helper.rb
ADDED
|
@@ -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
|
+
|