Skip to content

Commit

Permalink
Refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
glebm committed Nov 17, 2012
1 parent bfc65d6 commit 6e00449
Show file tree
Hide file tree
Showing 25 changed files with 439 additions and 303 deletions.
3 changes: 2 additions & 1 deletion Rakefile
Expand Up @@ -13,10 +13,11 @@ task :env do
include ToSpreadsheet::Helpers
end

desc 'Generate a simple xlsx file'
task :write_test_xlsx => :env do
require 'haml'
path = '/tmp/spreadsheet.xlsx'
html = Haml::Engine.new(File.read('spec/support/table.html.haml')).render
ToSpreadsheet::Axlsx::Renderer.to_package(html).serialize(path)
ToSpreadsheet::Renderer.to_package(html).serialize(path)
puts "Written to #{path}"
end
4 changes: 1 addition & 3 deletions lib/to_spreadsheet.rb
Expand Up @@ -7,8 +7,6 @@

module ToSpreadsheet
class << self
attr_accessor :context

def theme(name, &formats)
@themes ||= {}
if formats
Expand All @@ -21,4 +19,4 @@ def theme(name, &formats)
end

require 'to_spreadsheet/themes/default'
ToSpreadsheet.context = ToSpreadsheet::Context.new.apply ToSpreadsheet.theme(:default)
ToSpreadsheet::Context.global.format_xls ToSpreadsheet.theme(:default)
8 changes: 6 additions & 2 deletions lib/to_spreadsheet/action_pack_renderers.rb
Expand Up @@ -2,14 +2,18 @@
require 'action_controller/metal/renderers'
require 'action_controller/metal/responder'

require 'to_spreadsheet/axlsx/renderer'
require 'to_spreadsheet/renderer'

# This will let us do thing like `render :xlsx => 'index'`
# This is similar to how Rails internally implements its :json and :xml renderers
ActionController::Renderers.add :xlsx do |template, options|
filename = options[:filename] || options[:template] || 'data'

html = with_context ToSpreadsheet.context.derive do
html = with_context ToSpreadsheet::Context.global.merge(ToSpreadsheet::Context.new) do
# local context
@local_formats.each do |selector, &block|
context.process_dsl selector, &block
end if @local_formats
render_to_string(options[:template], options)
end

Expand Down
86 changes: 0 additions & 86 deletions lib/to_spreadsheet/axlsx/formatter.rb

This file was deleted.

52 changes: 0 additions & 52 deletions lib/to_spreadsheet/axlsx/renderer.rb

This file was deleted.

153 changes: 67 additions & 86 deletions lib/to_spreadsheet/context.rb
@@ -1,134 +1,115 @@
require 'to_spreadsheet/context/pairing'
require 'to_spreadsheet/formats'
require 'to_spreadsheet/rule'
require 'to_spreadsheet/rule/base'
require 'to_spreadsheet/rule/container'
require 'to_spreadsheet/rule/format'
require 'to_spreadsheet/rule/default_value'
require 'to_spreadsheet/rule/sheet'
require 'to_spreadsheet/rule/workbook'

module ToSpreadsheet
# This is the DSL context for `format_xls`
# It maintains the current formats set to enable for local and nested `format_xls` blocks
class Context
# todo (cleaner code): split features further into modules
# todo (extensibility): add processing callbacks (internal API)
include Pairing
attr_accessor :rules

def initialize(wb_options = nil)
@formats = []
@current_format = Formats.new
workbook wb_options if wb_options
end

# Returns a new formats jar for a given sheet
def formats(sheet)
format = Formats.new
@formats.each do |v|
sel, fmt = v[0], v[1]
format.merge!(fmt) if selects?(sel, sheet)
class << self
def global
@global ||= new
end
format
end

# Check if selector matches a given sheet / cell / row
def selects?(selector, entity)
return true if !selector
type, val = selector[0], selector[1]
sheet = entity.is_a?(::Axlsx::Workbook) ? entity : (entity.respond_to?(:workbook) ? entity.workbook : entity.worksheet.workbook)
doc = node_from_entity(sheet)
case type
when :css
doc.css(val).include?(node_from_entity(entity))
when :column
return false if entity.is_a?(Axlsx::Row)
entity.index == val if entity.is_a?(Axlsx::Cell)
when :row
return entity.index == val if entity.is_a?(Axlsx::Row)
entity.row.index == val if entity.is_a?(Axlsx::Cell)
when :range
if entity.is_a?(Axlsx::Cell)
pos = entity.pos
top_left, bot_right = val.split(':').map { |s| Axlsx.name_to_indices(s) }
pos[0] >= top_left[0] && pos[0] <= bot_right[0] && pos[1] >= top_left[1] && pos[1] <= bot_right[1]
end
end
def initialize(wb_options = nil)
@rules = []
workbook wb_options if wb_options
end

# current format, used internally
attr_accessor :current_format

# format_xls 'table.zebra' do
# format 'td', lambda { |cell| {b: true} if cell.row.even? }
# end
# Examples:
# format_xls 'table.zebra' do
# format 'td', lambda { |cell| {b: true} if cell.row.even? }
# end
# format_xls ToSpreadsheet.theme(:a_theme)
# format_xls 'table.zebra', ToSpreadsheet.theme(:zebra)
def format_xls(selector = nil, theme = nil, &block)
selector, theme = nil, selector if selector.is_a?(Proc) && !theme
add_format(selector, &theme) if theme
add_format(selector, &block) if block
process_dsl(selector, &theme) if theme
process_dsl(selector, &block) if block
self
end

def process_dsl(selector, &block)
@rule_container = add_rule :container, *selector_query(selector)
instance_eval(&block)
@rule_container = nil
end

def workbook(selector = nil, value)
add_rule :workbook, *selector_query(selector), value
end

# format 'td.b', b: true # bold
# format column: 0, width: 50
# format 'A1:C30', b: true
# Accepted properties: http://rubydoc.info/github/randym/axlsx/Axlsx/Cell
# column format also accepts Axlsx columnInfo settings
def format(selector = nil, options)
options = options.dup
selector = extract_selector!(selector, options)
add selector[0], selector, options
selector = selector_query(selector, options)
add_rule :format, *selector, options
end

# sheet 'table.landscape', page_setup: { orientation: landscape }
def sheet(selector = nil, options)
options = options.dup
selector = extract_selector!(selector, options)
add :sheet, selector, options
selector = selector_query(selector, options)
add_rule :sheet, *selector, options
end

# default 'td.c', 5
def default(selector, value)
options = {default_value: value}
selector = extract_selector!(selector, options)
add selector[0], selector, options
end

def add(setting, selector, value)
@current_format[setting] << [selector.try(:[], 1), value] if selector || value
end

def workbook(selector = nil, value)
add :package, selector, value
selector = selector_query(selector)
add_rule :default_value, *selector, value
end

def apply(theme = nil, &block)
add_format &theme if theme
add_format &block if block
self
def add_rule(rule_type, selector_type, selector_value, options = {})
rule = ToSpreadsheet::Rule.make(rule_type, selector_type, selector_value, options)
if @rule_container
@rule_container.children << rule
else
@rules << rule
end
rule
end

def derive
derived = dup
derived.current_format = derived.current_format.derive
derived
# A new context
def merge(other_context)
ctx = Context.new()
ctx.rules = rules + other_context.rules
ctx
end

private

def add_format(sheet_sel = nil, &block)
format_was = @current_format
@current_format = @current_format.derive
instance_eval &block
@formats << [extract_selector!(sheet_sel), @current_format]
@current_format = format_was
end

def extract_selector!(selector, options = {})
if selector
if selector =~ /:/ && selector[0].upcase == selector[0]
return [:range, selector]
# Extract selector query from DSL arguments
#
# Figures out text type:
# selector_query('td.num') # [:css, "td.num"]
# selector_query('A0:B5') # [:range, "A0:B5"]
#
# If text is nil, extracts first of row, range, and css keys
# selector_query(nil, {column: 0}] # [:column, 0]
def selector_query(text, opts = {})
if text
if text =~ /:/ && text[0].upcase == text[0]
return [:range, text]
else
return [:css, selector]
return [:css, text]
end
end
[:column, :row, :range].each do |key|
return [key, options.delete(key)] if options.key?(key)
end
selector
key = [:column, :row, :range].detect { |key| opts.key?(key) }
return [key, opts.delete(key)] if key
[nil, nil]
end
end
end

0 comments on commit 6e00449

Please sign in to comment.