PageRenderTime 23ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/plugins/cucumber/lib/cucumber/ast/table.rb

https://github.com/texel/rails-sender
Ruby | 334 lines | 199 code | 49 blank | 86 comment | 5 complexity | 82bb6c125104c04095f09979973bb4ec MD5 | raw file
Possible License(s): MIT
  1. module Cucumber
  2. module Ast
  3. # Holds the data of a table parsed from a feature file:
  4. #
  5. # | a | b |
  6. # | c | d |
  7. #
  8. # This gets parsed into a Table holding the values <tt>[['a', 'b'], ['c', 'd']]</tt>
  9. #
  10. class Table
  11. NULL_CONVERSIONS = Hash.new(lambda{ |cell_value| cell_value }).freeze
  12. attr_accessor :file
  13. def self.default_arg_name
  14. "table"
  15. end
  16. def initialize(raw, conversions = NULL_CONVERSIONS.dup)
  17. # Verify that it's square
  18. raw.transpose
  19. @raw = raw
  20. @cells_class = Cells
  21. @cell_class = Cell
  22. @conversion_procs = conversions
  23. end
  24. # Creates a copy of this table, inheriting the column mappings.
  25. def dup
  26. self.class.new(@raw.dup, @conversion_procs.dup)
  27. end
  28. # Returns a new, transposed table. Example:
  29. #
  30. # | a | 7 | 4 |
  31. # | b | 9 | 2 |
  32. #
  33. # Gets converted into the following:
  34. #
  35. # | a | b |
  36. # | 7 | 9 |
  37. # | 4 | 2 |
  38. #
  39. def transpose
  40. self.class.new(@raw.transpose, @conversion_procs.dup)
  41. end
  42. # Converts this table into an Array of Hash where the keys of each
  43. # Hash are the headers in the table. For example, a Table built from
  44. # the following plain text:
  45. #
  46. # | a | b | sum |
  47. # | 2 | 3 | 5 |
  48. # | 7 | 9 | 16 |
  49. #
  50. # Gets converted into the following:
  51. #
  52. # [{'a' => '2', 'b' => '3', 'sum' => '5'}, {'a' => '7', 'b' => '9', 'sum' => '16'}]
  53. #
  54. # Use #map_column! to specify how values in a column are converted.
  55. #
  56. def hashes
  57. @hashes ||= cells_rows[1..-1].map do |row|
  58. row.to_hash
  59. end
  60. end
  61. # Converts this table into a Hash where the first column is
  62. # used as keys and the second column is used as values
  63. #
  64. # | a | 2 |
  65. # | b | 3 |
  66. #
  67. # Gets converted into the following:
  68. #
  69. # {'a' => '2', 'b' => '3'}
  70. #
  71. # The table must be exactly two columns wide
  72. #
  73. def rows_hash
  74. verify_table_width(2)
  75. @rows_hash = self.transpose.hashes[0]
  76. end
  77. # Gets the raw data of this table. For example, a Table built from
  78. # the following plain text:
  79. #
  80. # | a | b |
  81. # | c | d |
  82. #
  83. # Get converted into the following:
  84. #
  85. # [['a', 'b], ['c', 'd']]
  86. #
  87. def raw
  88. @raw
  89. end
  90. # Same as #raw, but skips the first (header) row
  91. def rows
  92. @raw[1..-1]
  93. end
  94. def each_cells_row(&proc)
  95. cells_rows.each(&proc)
  96. end
  97. def accept(visitor)
  98. cells_rows.each do |row|
  99. visitor.visit_table_row(row)
  100. end
  101. nil
  102. end
  103. # For testing only
  104. def to_sexp #:nodoc:
  105. [:table, *cells_rows.map{|row| row.to_sexp}]
  106. end
  107. # Returns a new Table where the headers are redefined. This makes it
  108. # possible to use prettier header names in the features. Example:
  109. #
  110. # | Phone Number | Address |
  111. # | 123456 | xyz |
  112. # | 345678 | abc |
  113. #
  114. # A StepDefinition receiving this table can then map the columns:
  115. #
  116. # mapped_table = table.map_columns('Phone Number' => :phone, 'Address' => :address)
  117. # hashes = mapped_table.hashes
  118. # # => [{:phone => '123456', :address => 'xyz'}, {:phone => '345678', :address => 'abc'}]
  119. #
  120. def map_headers(mappings)
  121. table = self.dup
  122. table.map_headers!(mappings)
  123. table
  124. end
  125. # Change how #hashes converts column values. The +column_name+ argument identifies the column
  126. # and +conversion_proc+ performs the conversion for each cell in that column. If +strict+ is
  127. # true, an error will be raised if the column named +column_name+ is not found. If +strict+
  128. # is false, no error will be raised. Example:
  129. #
  130. # Given /^an expense report for (.*) with the following posts:$/ do |table|
  131. # posts_table.map_column!('amount') { |a| a.to_i }
  132. # posts_table.hashes.each do |post|
  133. # # post['amount'] is a Fixnum, rather than a String
  134. # end
  135. # end
  136. #
  137. def map_column!(column_name, strict=true, &conversion_proc)
  138. verify_column(column_name) if strict
  139. @conversion_procs[column_name] = conversion_proc
  140. end
  141. def to_hash(cells) #:nodoc:
  142. hash = Hash.new do |hash, key|
  143. hash[key.to_s] if key.is_a?(Symbol)
  144. end
  145. @raw[0].each_with_index do |column_name, column_index|
  146. value = @conversion_procs[column_name].call(cells.value(column_index))
  147. hash[column_name] = value
  148. end
  149. hash
  150. end
  151. def index(cells) #:nodoc:
  152. cells_rows.index(cells)
  153. end
  154. def verify_column(column_name)
  155. raise %{The column named "#{column_name}" does not exist} unless @raw[0].include?(column_name)
  156. end
  157. def verify_table_width(width)
  158. raise %{The table must have exactly #{width} columns} unless @raw[0].size == width
  159. end
  160. def arguments_replaced(arguments) #:nodoc:
  161. raw_with_replaced_args = raw.map do |row|
  162. row.map do |cell|
  163. cell_with_replaced_args = cell
  164. arguments.each do |name, value|
  165. if cell_with_replaced_args && cell_with_replaced_args.include?(name)
  166. cell_with_replaced_args = value ? cell_with_replaced_args.gsub(name, value) : nil
  167. end
  168. end
  169. cell_with_replaced_args
  170. end
  171. end
  172. Table.new(raw_with_replaced_args)
  173. end
  174. def has_text?(text)
  175. raw.flatten.detect{|cell_value| cell_value.index(text)}
  176. end
  177. def cells_rows
  178. @rows ||= cell_matrix.map do |cell_row|
  179. @cells_class.new(self, cell_row)
  180. end
  181. end
  182. def headers
  183. @raw.first
  184. end
  185. def header_cell(col)
  186. cells_rows[0][col]
  187. end
  188. protected
  189. def map_headers!(mappings)
  190. headers = @raw[0]
  191. mappings.each_pair do |pre, post|
  192. headers[headers.index(pre)] = post
  193. if @conversion_procs.has_key?(pre)
  194. @conversion_procs[post] = @conversion_procs.delete(pre)
  195. end
  196. end
  197. end
  198. private
  199. def col_width(col)
  200. columns[col].__send__(:width)
  201. end
  202. def columns
  203. @columns ||= cell_matrix.transpose.map do |cell_row|
  204. @cells_class.new(self, cell_row)
  205. end
  206. end
  207. def cell_matrix
  208. row = -1
  209. @cell_matrix ||= @raw.map do |raw_row|
  210. line = raw_row.line rescue -1
  211. row += 1
  212. col = -1
  213. raw_row.map do |raw_cell|
  214. col += 1
  215. @cell_class.new(raw_cell, self, row, col, line)
  216. end
  217. end
  218. end
  219. # Represents a row of cells or columns of cells
  220. class Cells
  221. include Enumerable
  222. attr_reader :exception
  223. def initialize(table, cells)
  224. @table, @cells = table, cells
  225. end
  226. def accept(visitor)
  227. each do |cell|
  228. visitor.visit_table_cell(cell)
  229. end
  230. nil
  231. end
  232. # For testing only
  233. def to_sexp #:nodoc:
  234. [:row, line, *@cells.map{|cell| cell.to_sexp}]
  235. end
  236. def to_hash #:nodoc:
  237. @to_hash ||= @table.to_hash(self)
  238. end
  239. def value(n) #:nodoc:
  240. self[n].value
  241. end
  242. def [](n)
  243. @cells[n]
  244. end
  245. def line
  246. @cells[0].line
  247. end
  248. def dom_id
  249. "row_#{line}"
  250. end
  251. private
  252. def index
  253. @table.index(self)
  254. end
  255. def width
  256. map{|cell| cell.value ? cell.value.to_s.jlength : 0}.max
  257. end
  258. def each(&proc)
  259. @cells.each(&proc)
  260. end
  261. end
  262. class Cell
  263. attr_reader :value, :line
  264. attr_writer :status
  265. def initialize(value, table, row, col, line)
  266. @value, @table, @row, @col, @line = value, table, row, col, line
  267. end
  268. def accept(visitor)
  269. visitor.visit_table_cell_value(@value, col_width, @status)
  270. end
  271. def header_cell
  272. @table.header_cell(@col)
  273. end
  274. # For testing only
  275. def to_sexp #:nodoc:
  276. [:cell, @value]
  277. end
  278. private
  279. def col_width
  280. @col_width ||= @table.__send__(:col_width, @col)
  281. end
  282. end
  283. end
  284. end
  285. end