rbbt-rest 1.1.4 → 1.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +8 -8
  2. data/config.ru +3 -0
  3. data/lib/rbbt/rest/client.rb +9 -10
  4. data/lib/rbbt/rest/common/cache.rb +32 -7
  5. data/lib/rbbt/rest/common/forms.rb +33 -0
  6. data/lib/rbbt/rest/common/locate.rb +4 -0
  7. data/lib/rbbt/rest/common/misc.rb +24 -0
  8. data/lib/rbbt/rest/common/render.rb +26 -9
  9. data/lib/rbbt/rest/common/resources.rb +1 -1
  10. data/lib/rbbt/rest/common/table.rb +87 -26
  11. data/lib/rbbt/rest/common/users.rb +5 -1
  12. data/lib/rbbt/rest/entity.rb +300 -95
  13. data/lib/rbbt/rest/entity/action_controller.rb +6 -1
  14. data/lib/rbbt/rest/entity/entity_list_card.rb +1 -1
  15. data/lib/rbbt/rest/entity/entity_map_card.rb +15 -0
  16. data/lib/rbbt/rest/entity/favourites.rb +57 -1
  17. data/lib/rbbt/rest/entity/finder.rb +1 -1
  18. data/lib/rbbt/rest/entity/helpers.rb +40 -7
  19. data/lib/rbbt/rest/entity/list.rb +8 -0
  20. data/lib/rbbt/rest/entity/locate.rb +114 -0
  21. data/lib/rbbt/rest/entity/map.rb +99 -0
  22. data/lib/rbbt/rest/entity/render.rb +25 -0
  23. data/lib/rbbt/rest/entity/rest.rb +33 -4
  24. data/lib/rbbt/rest/main.rb +27 -4
  25. data/lib/rbbt/rest/workflow.rb +7 -1
  26. data/lib/rbbt/rest/workflow/jobs.rb +21 -1
  27. data/share/views/compass/app.sass +17 -9
  28. data/share/views/compass/entity_card.sass +16 -3
  29. data/share/views/compass/favourites.sass +44 -2
  30. data/share/views/compass/form.sass +11 -0
  31. data/share/views/compass/list_container.sass +1 -1
  32. data/share/views/compass/reveal.sass +31 -0
  33. data/share/views/compass/style.sass +29 -1
  34. data/share/views/compass/style_mixins.sass +18 -6
  35. data/share/views/compass/table.sass +19 -3
  36. data/share/views/compass/topbar.sass +8 -2
  37. data/share/views/entity_map/Default.haml +5 -0
  38. data/share/views/entity_partials/action_card.haml +2 -0
  39. data/share/views/entity_partials/action_controller.haml +8 -1
  40. data/share/views/entity_partials/entity_list_card.haml +8 -6
  41. data/share/views/entity_partials/entity_map_card.haml +63 -0
  42. data/share/views/entity_partials/list_container.haml +3 -2
  43. data/share/views/error.haml +14 -10
  44. data/share/views/help.haml +34 -0
  45. data/share/views/help/entity.haml +193 -0
  46. data/share/views/help/workflow.haml +77 -0
  47. data/share/views/job_result/tsv.haml +3 -2
  48. data/share/views/layout.haml +33 -3
  49. data/share/views/layout/favourites.haml +14 -4
  50. data/share/views/layout/header.haml +4 -0
  51. data/share/views/partials/form.haml +3 -1
  52. data/share/views/partials/table.haml +16 -52
  53. data/share/views/partials/table/column.haml +10 -0
  54. data/share/views/partials/table/files.haml +4 -0
  55. data/share/views/partials/table/filters.haml +11 -0
  56. data/share/views/partials/table/page.haml +37 -0
  57. data/share/views/public/favicon.gif +0 -0
  58. data/share/views/public/js/_ajax_replace.js +76 -4
  59. data/share/views/public/js/_fix_tablesorter_science.js +13 -0
  60. data/share/views/public/js/actions.js +8 -2
  61. data/share/views/public/js/base.js +12 -1
  62. data/share/views/public/js/entities.js +39 -7
  63. data/share/views/public/js/favourites.js +308 -77
  64. data/share/views/public/js/helpers.js +9 -5
  65. data/share/views/public/js/lists.js +12 -12
  66. data/share/views/public/js/maps.js +17 -0
  67. data/share/views/public/js/reveal.js +48 -0
  68. data/share/views/public/js/tables.js +110 -51
  69. data/share/views/public/js/workflow.js +4 -16
  70. data/share/views/public/plugins/underscore/js/underscore.js +1227 -0
  71. data/share/views/tasks.haml +12 -1
  72. data/share/views/wait.haml +7 -0
  73. metadata +17 -2
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MjY4NGQ1Mjg4YTIyYmRjYWQ0OWFkMjQ4NmQxMjU3NmIwYjIzN2M1Yw==
4
+ YmU4OTJhZGMwZTZkYzY0MTcxZjVlNjNlM2QyYjc2YTMyOWIyYjc0MA==
5
5
  data.tar.gz: !binary |-
6
- MTQ3MGVlMzE3NjU2ZDc3ZmI4YjM4MDA1MjY0ZDI4NjFkYTY5ZDAxOQ==
6
+ YjIwNDcyMzgzMWViOGY4NTI1ZTM5YjkzZTA5Y2Q1MzdlYjJmNzk4Nw==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- OTg5MGEwZmQ5YmYyOWRmNzM2YjQ4OTZkNzI1YjRiZGQ5ZjkzYWRhMjJjNTVj
10
- ZTliMjJlYWE0YjJhOGI1ZTdhZWVkNzM4MjFmODllZGRhZDA2OGE3NjQ1ZDQ5
11
- ZTBmYTkyOTY2ZDAxNzlmMGYzYzFiZjY2NmFlN2Q5M2M1OWM1NmU=
9
+ NmZjNDljMWQxYTc3NTEwYjJkOTgyNWFhNjcyZjI3ODA2YTk4NzYzNTdiZDA1
10
+ ZjY0MTE1YmQxZDdhMWI2MmRkMmJmNzMxMWQ0MTIyMThlOGY3ODc0Njg4NGY0
11
+ NzgyYTU4MDcyOTkyYmM3OGE2YjM4MDQ3MTM2ZTkwM2YxYzJkNWE=
12
12
  data.tar.gz: !binary |-
13
- ZTQ3Y2Q5YTFjOGQ4YTlmMzlhMmJhNTUwZTFlODUxNjhjYjEwNzBmZDg2YmNk
14
- OWI2OGQzNzI1MzNmYTQzYzVjYWJlYmZiNWY2NDY5NGRmYWE2OWQ4ZjE4YjBm
15
- MDliZDczODJmNjdlYWNiODZiOGY3Mzk3MTFjZGFkMDMzZjBhYTU=
13
+ ZjVlOGQxMTBkZGY3N2IxYTgwODU3MDc5NmE4OGIyYzMyY2I1NDdhNDc5ODZk
14
+ NzFlM2FlMDhjMzhlNDU0Y2QzZjJmZjUxOGQyMGM2NmNhN2U2ZWIzMzNmN2Uz
15
+ MGE0MDZlNDRlMWVjY2U2NTE2ZjRkM2Y3NGE5MGI4OTY2ZTI3OTk=
data/config.ru CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'zurb-foundation'
2
2
  require 'modular-scale'
3
3
 
4
+ require 'sinatra'
5
+
4
6
  require 'rbbt'
5
7
  require 'rbbt/rest/main'
6
8
  require 'rbbt/rest/entity'
@@ -95,5 +97,6 @@ end
95
97
 
96
98
  #{{{ RUN
97
99
  $title = "RbbtRest"
100
+
98
101
  use Rack::Deflater
99
102
  run RbbtRest
@@ -36,7 +36,11 @@ class WorkflowRESTClient
36
36
  def self.get_json(url, params = {})
37
37
  Log.debug("RestClient get_json #{}: #{ url } - #{params.inspect}")
38
38
  params = params.merge({ :_format => 'json' })
39
- res = RestClient.get(url, :params => params)
39
+ begin
40
+ res = RestClient.get(url, :params => params)
41
+ rescue => e
42
+ raise JSON.parse(e.response)["message"]
43
+ end
40
44
  begin
41
45
  JSON.parse(res)
42
46
  rescue
@@ -175,6 +179,10 @@ class WorkflowRESTClient
175
179
  name
176
180
  end
177
181
 
182
+ def workflow_description
183
+ WorkflowRESTClient.get_raw(File.join(url, 'description'))
184
+ end
185
+
178
186
  def task_info(task)
179
187
  @task_info ||= {}
180
188
  @task_info[task]
@@ -301,12 +309,3 @@ class WorkflowRESTClient
301
309
  end
302
310
  end
303
311
  end
304
-
305
- if __FILE__ == $0
306
- client = WorkflowRESTClient.new("http://darthcaedus:9292/Sequence", "Sequence")
307
-
308
- job = client.job(:genes_at_genomic_positions, "1", :organism => "Hsa", :positions => ["2:198266834:R"]).clean.fork
309
- puts job.join.load.to_s
310
-
311
- end
312
-
@@ -14,6 +14,7 @@ module RbbtRESTHelpers
14
14
  end
15
15
 
16
16
  def cache(name, params = {}, &block)
17
+ @cache_type ||= params[:cache_type] if params[:cache_type]
17
18
  return yield if name.nil? or cache_type.nil? or cache_type == :none
18
19
 
19
20
  send_file = consume_parameter(:_send_file, params)
@@ -29,22 +30,43 @@ module RbbtRESTHelpers
29
30
 
30
31
  step = Step.new(path, task, nil, nil, self)
31
32
 
33
+ halt 200, step.info.to_json if @format == :info
34
+
32
35
  self.instance_variable_set("@step", step)
33
36
 
34
37
  if @fragment
35
38
  fragment_file = step.file(@fragment)
36
39
  if File.exists?(fragment_file)
37
40
  case @format.to_s
38
- when "tsv"
39
- content_type "text/tab-separated-values"
40
- send_file fragment_file
41
41
  when "table"
42
42
  halt 200, tsv2html(fragment_file)
43
43
  when "json"
44
- halt 200, tsv_process(TSV.open(fragment_file)).to_json
44
+ halt 200, tsv_process(load_tsv(fragment_file).first).to_json
45
+ when "tsv"
46
+ content_type "text/tab-separated-values"
47
+ halt 200, tsv_process(load_tsv(fragment_file).first).to_s
48
+ when "entities"
49
+ tsv = tsv_process(load_tsv(fragment_file).first)
50
+ list = tsv.values.flatten
51
+ tsv.prepare_entity(list, tsv.fields.first, tsv.entity_options)
52
+ type = list.annotation_types.last
53
+ list_id = "List of #{type} in table #{ @fragment }"
54
+ list_id << " (#{ @filter })" if @filter
55
+ Entity::List.save_list(type.to_s, list_id, list, user)
56
+ header "Location", Entity::REST.entity_list_url(list_id, type)
57
+ redirect to(Entity::REST.entity_list_url(list_id, type))
58
+ when "map"
59
+ tsv = tsv_process(load_tsv(fragment_file).first)
60
+ type = tsv.keys.annotation_types.last
61
+ column = tsv.fields.first
62
+ map_id = "Map #{type}-#{column} in #{ @fragment }"
63
+ map_id << " (#{ @filter.gsub(';','|') })" if @filter
64
+ Entity::Map.save_map(type.to_s, column, map_id, tsv, user)
65
+ url = Entity::REST.entity_map_url(map_id, type, column)
66
+ redirect to(url)
45
67
  when "excel"
46
68
  require 'rbbt/tsv/excel'
47
- tsv = TSV.open(Open.open(fragment_file))
69
+ tsv = load_tsv(fragment_file).first
48
70
  content_type "text/html"
49
71
  data = nil
50
72
  excel_file = TmpFile.tmp_file
@@ -89,9 +111,12 @@ module RbbtRESTHelpers
89
111
  begin
90
112
  case step.status
91
113
  when :error, :aborted
92
- error_for step, false
114
+ error_for step, !@ajax
93
115
  when :done
94
- if send_file
116
+ case
117
+ when @permalink
118
+ redirect to(permalink(step.path))
119
+ when send_file
95
120
  send_file step.path
96
121
  else
97
122
  step.load
@@ -25,6 +25,39 @@ module RbbtRESTHelpers
25
25
  html_options = consume_parameter(:html_options, extra) || {}
26
26
 
27
27
  case type
28
+ when :multiple
29
+ choices = consume_parameter(:choices, extra)
30
+ default = default.collect{|o| o.to_s} if default
31
+ current = current.collect{|o| o.to_s} if current
32
+ input_label(id, name, description, Array === default ? default * ", " : nil, extra) +
33
+ choices.collect do |choice|
34
+ choice_name = name.to_s + "[#{ choice }]"
35
+
36
+ check_true = (current && current.include?(choice.to_s)) || (default && default.include?(choice.to_s))
37
+
38
+ check_true = false if check_true.nil?
39
+ check_false = ! check_true
40
+
41
+ choice_id = id + "[#{ Misc.snake_case(choice) }]"
42
+ if id
43
+ false_id = choice_id + '_false'
44
+ true_id = choice_id + '_true'
45
+ else
46
+ false_id = nil
47
+ true_id = nil
48
+ end
49
+
50
+ #choice_html = html_tag("input", nil, :type => :hidden, :name => choice_name + "_checkbox_false", :value => "false") +
51
+ # html_tag("input", nil, :type => :checkbox, :checked => check_true, :name => choice_name, :value => "true", :id => choice_id) +
52
+ # input_label(choice_id, choice, nil, nil, extra.merge({:class => :inline}))
53
+
54
+ choice_html = html_tag("input", nil, :type => :checkbox, :checked => check_true, :name => choice_name, :value => "true", :id => choice_id) +
55
+ input_label(choice_id, choice, choice, nil, extra.merge({:class => :inline}))
56
+
57
+ html_tag('span', choice_html, :class => 'choice')
58
+ end * "\n"
59
+
60
+
28
61
  when :boolean
29
62
  current = param2boolean(current) unless current.nil?
30
63
  default = param2boolean(default) unless default.nil?
@@ -26,6 +26,10 @@ module RbbtRESTHelpers
26
26
 
27
27
  #{{{ SASS
28
28
 
29
+ def self.add_sass_load_path(path)
30
+ Sass::Engine::DEFAULT_OPTIONS[:load_paths].unshift path
31
+ end
32
+
29
33
  def self.sass_resources
30
34
  @sass_resources ||= [Rbbt.share.views.compass.find(:lib)]
31
35
  end
@@ -5,6 +5,14 @@ module RbbtRESTHelpers
5
5
 
6
6
  PAGE_SIZE = 20
7
7
 
8
+ def log(status, message)
9
+ if @step
10
+ @step.log(status, message)
11
+ else
12
+ Log.debug("[#{ status }] #{ message }")
13
+ end
14
+ end
15
+
8
16
  def production?
9
17
  ENV["RACK_ENV"] == "production"
10
18
  end
@@ -41,6 +49,9 @@ module RbbtRESTHelpers
41
49
  @debug_js = consume_parameter(:_debug_js)
42
50
  @debug_js = false if @debug_js.nil? or @debug_js == "false"
43
51
 
52
+ @debug_css = consume_parameter(:_debug_css)
53
+ @debug_css = false if @debug_css.nil? or @debug_css == "false"
54
+
44
55
  @_ = consume_parameter(:_)
45
56
 
46
57
  @fragment = consume_parameter(:_fragment)
@@ -65,6 +76,8 @@ module RbbtRESTHelpers
65
76
  end
66
77
  end
67
78
 
79
+ @permalink = consume_parameter :_permalink
80
+
68
81
  @clean_params = params.dup
69
82
  end
70
83
 
@@ -91,6 +104,9 @@ module RbbtRESTHelpers
91
104
  when :float
92
105
  value.to_f
93
106
 
107
+ when :multiple
108
+ value.keys
109
+
94
110
  when :boolean
95
111
  param2boolean(value)
96
112
 
@@ -168,4 +184,12 @@ module RbbtRESTHelpers
168
184
  } * "\n"
169
185
  html_tag(:dl, entries, options)
170
186
  end
187
+
188
+ def permalink(path)
189
+ id = Misc.digest(Time.now.to_s)
190
+ dest = File.join(settings.permalink_dir, id)
191
+ FileUtils.mkdir_p settings.permalink_dir unless File.exists? settings.permalink_dir
192
+ FileUtils.ln_s(path, dest)
193
+ "/permalink/#{ id }"
194
+ end
171
195
  end
@@ -3,13 +3,17 @@ require 'rbbt/rest/common/cache'
3
3
 
4
4
  module RbbtRESTHelpers
5
5
  def error_for(job, layout = nil)
6
- layout = @layout if layout.nil?
7
- layout_file = (layout ? locate_template('layout') : nil)
8
- template_file = locate_template('error')
6
+ if @format == :json
7
+ halt 400, {"message" => job.messages[-2], "backtrace" => job.info[:backtrace]}.to_json
8
+ else
9
+ layout = @layout if layout.nil?
10
+ layout_file = (layout ? locate_template('layout') : nil)
11
+ template_file = locate_template('error')
9
12
 
10
- result = render template_file, {:job => job}, layout_file
13
+ result = render template_file, {:job => job}, layout_file
11
14
 
12
- halt 500, result
15
+ halt 400, result
16
+ end
13
17
  end
14
18
 
15
19
  def wait_on(job, layout = nil)
@@ -29,16 +33,16 @@ module RbbtRESTHelpers
29
33
 
30
34
  def render(template_file, locals = {}, layout_file = nil, cache = nil, cache_options = {})
31
35
  if layout_file
32
- Haml::Engine.new(Open.read(layout_file), :filename => layout_file).render(self) do
36
+ Haml::Engine.new(Open.read(layout_file), :filename => layout_file, :ugly => production?).render(self, locals) do
33
37
  cache(cache, locals.merge(:_template_file => template_file, :user => user).merge(cache_options)) do
34
38
  Log.debug("Rendering #{template_file} with layout")
35
- Haml::Engine.new(Open.read(template_file), :filename => template_file).render(self, locals)
39
+ Haml::Engine.new(Open.read(template_file), :filename => template_file, :ugly => production?).render(self, locals)
36
40
  end
37
41
  end
38
42
  else
39
43
  cache(cache, locals.merge(:_template_file => template_file, :user => user).merge(cache_options)) do
40
44
  Log.debug("Rendering #{template_file} without layout")
41
- Haml::Engine.new(Open.read(template_file), :filename => template_file).render(self, locals)
45
+ Haml::Engine.new(Open.read(template_file), :filename => template_file, :ugly => production?).render(self, locals)
42
46
  end
43
47
  end
44
48
  end
@@ -80,7 +84,7 @@ module RbbtRESTHelpers
80
84
  url = remove_GET_param(url, "_update")
81
85
  fragment_url = add_GET_param(url, "_fragment", fragment_code)
82
86
  if link.nil?
83
- html_tag('a', " ", :href => fragment_url, :class => 'fragment')
87
+ html_tag('a', "", :href => fragment_url, :class => 'fragment')
84
88
  else
85
89
  if link =~ / href=/
86
90
  link.sub(/ href=('|")/," href='#{fragment_url}'")
@@ -143,5 +147,18 @@ module RbbtRESTHelpers
143
147
  raise "Type not understood: #{ type }"
144
148
  end
145
149
  end
150
+
151
+ def reveal(text, id = nil, &block)
152
+ id ||= "rbbt_reveal_" << (rand * 10000).to_i.to_s
153
+
154
+ content_html = capture_haml(&block)
155
+
156
+ str = html_tag('a', text.to_s, :href => "#", "data-reveal-id" => 'modal1', 'attr-reveal_id' => id, 'class' => 'rbbt_reveal_trigger') <<
157
+ "\n" <<
158
+ html_tag('div', "\n" << content_html << "\n", :id => id, 'class' => 'rbbt_reveal_content') <<
159
+ "\n"
160
+
161
+ str
162
+ end
146
163
  end
147
164
 
@@ -1,5 +1,4 @@
1
1
  require 'sass'
2
- #require 'yui/compressor'
3
2
  require 'uglifier'
4
3
 
5
4
  module RbbtRESTHelpers
@@ -22,6 +21,7 @@ module RbbtRESTHelpers
22
21
 
23
22
  def link_css(file)
24
23
  file += '.css' unless file =~ /.css$/
24
+ file << "?_update=reload" if @debug_css
25
25
  html_tag('link', nil, :rel => 'stylesheet', :type => 'text/css', :href => file)
26
26
  end
27
27
 
@@ -1,5 +1,24 @@
1
1
  require 'rbbt/entity'
2
2
 
3
+ module Link
4
+ extend Entity
5
+
6
+ def self.tsv_sort(v)
7
+ value = v.last
8
+ value = value.first if Array === value
9
+ if value and value.match(/<a [^>]*>([^>]*)<\/a>/)
10
+ val = $1
11
+ if val =~ /^\s*\d/
12
+ val.to_f
13
+ else
14
+ val
15
+ end
16
+ else
17
+ ""
18
+ end
19
+ end
20
+ end
21
+
3
22
  module NumericValue
4
23
  extend Entity
5
24
 
@@ -20,11 +39,17 @@ t.value
20
39
  t.values
21
40
  adjusted.p.value
22
41
  adjusted.p.values
42
+ Rank
43
+ rank
23
44
  EOF
24
45
  ).split("\n")
25
46
 
26
47
  def <=>(other)
27
- self.to_f <=> other.to_f
48
+ if Float === self
49
+ super(other.to_f)
50
+ else
51
+ self.to_f <=> other.to_f
52
+ end
28
53
  end
29
54
 
30
55
  def self.tsv_sort(v)
@@ -35,6 +60,10 @@ adjusted.p.values
35
60
  value.to_f
36
61
  end
37
62
  end
63
+
64
+ def to_s
65
+ "%.5g" % self.to_f
66
+ end
38
67
  end
39
68
 
40
69
  module RbbtRESTHelpers
@@ -44,13 +73,13 @@ module RbbtRESTHelpers
44
73
  tsv.collect{|id, value| [id, value]}
45
74
  when :list
46
75
  key_field = tsv.key_field
47
- tsv.collect{|id, values| values = NamedArray.setup([id].concat(values), values.fields, id, values.entity_options, values.entity_templates); values.fields = [key_field].concat values.fields if values.respond_to? :fields; values }
76
+ tsv.collect{|id, values| new_values = [id].concat(values); begin NamedArray.setup(new_values, values.fields, id, values.entity_options, values.entity_templates); new_values.fields = [key_field].concat values.fields end if values.respond_to? :fields; new_values }
48
77
  when :flat
49
78
  key_field = tsv.key_field
50
79
  tsv.collect{|id, values| [id, values]}
51
80
  when :double
52
81
  key_field = tsv.key_field
53
- tsv.collect{|id, value_lists| value_lists = NamedArray.setup([id].concat(value_lists), value_lists.fields, id, value_lists.entity_options, value_lists.entity_templates); value_lists.fields = ([key_field].concat value_lists.fields) if value_lists.respond_to? :fields; value_lists }
82
+ tsv.collect{|id, value_lists| new_value_lists = [id].concat(value_lists); begin NamedArray.setup(new_value_lists, value_lists.fields, id, value_lists.entity_options, value_lists.entity_templates); new_value_lists.fields = ([key_field].concat value_lists.fields) end if value_lists.respond_to? :fields; new_value_lists }
54
83
  end
55
84
  end
56
85
 
@@ -78,7 +107,11 @@ module RbbtRESTHelpers
78
107
  reverse = false
79
108
  end
80
109
 
81
- entity = Entity.formats[field]
110
+ if object.entity_templates[field]
111
+ entity = object.entity_templates[field].annotation_types.last
112
+ else
113
+ entity = Entity.formats[field]
114
+ end
82
115
 
83
116
  num = num.to_i
84
117
  size = size.to_i
@@ -99,13 +132,13 @@ module RbbtRESTHelpers
99
132
  column = @column if column.nil?
100
133
 
101
134
  if filter and filter.to_s != "false"
102
- filter.split("|").each do |f|
135
+ filter.split(";;").each do |f|
103
136
  key, value = f.split("~")
104
137
  case
105
138
  when value =~ /^([<>]=?)(.*)/
106
- tsv = tsv.select(key){|k| k.send($1, $2)}
107
- when value =~ /^\/.*\/$/
108
- tsv = tsv.select(key => Regexp.new(value))
139
+ tsv = tsv.select(key){|k| k = k.first if Array === k; k.to_f.send($1, $2.to_f)}
140
+ when value =~ /^\/(.+)\/.{0,2}$/
141
+ tsv = tsv.select(key => Regexp.new($1))
109
142
  else
110
143
  tsv = tsv.select(key => value)
111
144
  end
@@ -119,26 +152,27 @@ module RbbtRESTHelpers
119
152
 
120
153
  def tsv_rows(tsv, page = nil, filter = nil, column = nil)
121
154
  tsv = tsv_process(tsv, filter, column)
155
+ length = tsv.size
122
156
  page = @page if page.nil?
123
157
  if page.nil? or page.to_s == "false"
124
- tsv_rows_full(tsv)
158
+ [tsv_rows_full(tsv), length]
125
159
  else
126
- tsv_rows_full(paginate(tsv, page))
160
+ [tsv_rows_full(paginate(tsv, page)), length]
127
161
  end
128
162
  end
129
163
 
130
164
 
131
165
  def table_value(value, type = nil, options = {})
132
166
  options = {} if options.nil?
133
- return value.list_link :length, options[:list_id] if Array === value and options[:list_id]
167
+ return value.list_link :length, options[:list_id] if Array === value and options[:list_id] and value.respond_to? :list_link
134
168
 
135
169
  entity_options = options[:entity_options]
136
170
 
137
- Misc.prepare_entity(value, type, entity_options) if Entity.formats.include? type
171
+ Misc.prepare_entity(value, type, entity_options) if Entity.formats.include? type and not options[:unnamed]
138
172
 
139
173
  value = value.link if value.respond_to? :link
140
174
 
141
- Array === value ? value * ", " : value
175
+ Array === value ? value.collect{|v| v.to_s} * ", " : value
142
176
  end
143
177
 
144
178
  def header(field, entity_type, entity_options = {})
@@ -151,12 +185,40 @@ module RbbtRESTHelpers
151
185
  @table_filters[field] = type
152
186
  end
153
187
 
188
+ def self.save_tsv(tsv, path)
189
+ Open.write(path, tsv.to_s)
190
+ table_options = {:tsv_entity_options => tsv.entity_options}
191
+ Open.write(path + '.table_options', table_options.to_yaml )
192
+ end
193
+
194
+ def self.load_tsv(file)
195
+ tsv = TSV.open(Open.open(file))
196
+
197
+ table_options = File.exists?(file + '.table_options') ? YAML.load_file(file + '.table_options') : {}
198
+ tsv.entity_options = table_options[:tsv_entity_options]
199
+ headers = table_options[:headers]
200
+ headers.each{|field,p| tsv.entity_templates[field] = Misc.prepare_entity("TEMPLATE", p.first, p.last) } unless headers.nil?
201
+
202
+ [tsv, table_options]
203
+ end
204
+
205
+ def save_tsv(file)
206
+ RbbtRESTHelpers.save_tsv(file)
207
+ end
208
+
209
+
210
+ def load_tsv(file)
211
+ RbbtRESTHelpers.load_tsv(file)
212
+ end
213
+
154
214
  def table(options = {})
155
215
  options = {} if options.nil?
156
216
 
157
217
  tsv = yield
158
218
 
159
- table_code = (rand * 100000).to_i.to_s
219
+ table_code = options[:table_id] || (rand * 100000).to_i.to_s
220
+ table_code = Entity::REST.clean_element(table_code)
221
+ table_code.sub!(/[^\w]/,'_')
160
222
  table_file = @step.file(table_code)
161
223
 
162
224
  url = add_GET_param(@fullpath, "_fragment", File.basename(table_file))
@@ -191,20 +253,19 @@ module RbbtRESTHelpers
191
253
  tsv2html(table_file)
192
254
  end
193
255
 
194
- def load_tsv(file)
195
- tsv
196
- end
197
-
198
- def tsv2html(file)
199
- tsv = TSV.open(Open.open(file))
200
256
 
201
- table_options = File.exists?(file + '.table_options') ? YAML.load_file(file + '.table_options') : {}
202
- tsv.entity_options = table_options[:tsv_entity_options]
203
- headers = table_options[:headers]
204
- filters = table_options[:filters]
205
- headers.each{|field,p| tsv.entity_templates[field] = Misc.prepare_entity("TEMPLATE", p.first, p.last) } unless headers.nil?
257
+ def tsv2html(file, default_table_options = {})
258
+ if TSV === file
259
+ tsv, table_options = file, {}
260
+ table_options[:unnamed] = tsv.unnamed
261
+ else
262
+ tsv, table_options = load_tsv(file)
263
+ end
206
264
 
207
265
  content_type "text/html"
208
- partial_render('partials/table', {:filters => filters, :total_size => tsv.size, :rows => tsv_rows(tsv), :header => tsv.all_fields, :table_options => table_options})
266
+ rows, length = tsv_rows(tsv)
267
+
268
+ table_options = default_table_options.merge(table_options)
269
+ partial_render('partials/table', {:total_size => length, :rows => rows, :header => tsv.all_fields, :table_options => table_options})
209
270
  end
210
271
  end