marty 0.5.15 → 0.5.16
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +27 -0
- data/.rspec +3 -0
- data/.travis.yml +23 -0
- data/Gemfile +23 -0
- data/INDEPENDENCE_ISSUES.md +23 -0
- data/app/assets/images/marty/.gitkeep +0 -0
- data/app/components/marty/report_form.rb +9 -4
- data/gemini_deprecations.md +6 -0
- data/lib/marty/data_change.rb +99 -0
- data/lib/marty/data_conversion.rb +11 -3
- data/lib/marty/data_exporter.rb +9 -0
- data/lib/marty/version.rb +1 -1
- data/marty.gemspec +35 -0
- data/script/rails +8 -0
- data/spec/controllers/application_controller_spec.rb +52 -0
- data/spec/controllers/job_controller_spec.rb +226 -0
- data/spec/controllers/rpc_controller_spec.rb +379 -0
- data/spec/controllers/rpc_import_spec.rb +45 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/controllers/components_controller.rb +7 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/models/gemini/amortization_type.rb +5 -0
- data/spec/dummy/app/models/gemini/bud_category.rb +7 -0
- data/spec/dummy/app/models/gemini/entity.rb +2 -0
- data/spec/dummy/app/models/gemini/extras/data_import.rb +5 -0
- data/spec/dummy/app/models/gemini/extras/settlement_import.rb +28 -0
- data/spec/dummy/app/models/gemini/fannie_bup.rb +29 -0
- data/spec/dummy/app/models/gemini/grouping.rb +8 -0
- data/spec/dummy/app/models/gemini/grouping_head_version.rb +14 -0
- data/spec/dummy/app/models/gemini/head.rb +7 -0
- data/spec/dummy/app/models/gemini/head_version.rb +14 -0
- data/spec/dummy/app/models/gemini/helper.rb +44 -0
- data/spec/dummy/app/models/gemini/loan_program.rb +11 -0
- data/spec/dummy/app/models/gemini/mortgage_type.rb +5 -0
- data/spec/dummy/app/models/gemini/simple.rb +6 -0
- data/spec/dummy/app/models/gemini/streamline_type.rb +16 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +82 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml.example +10 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +35 -0
- data/spec/dummy/config/environments/production.rb +69 -0
- data/spec/dummy/config/environments/test.rb +39 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/delayed_job.rb +5 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +12 -0
- data/spec/dummy/db/migrate/20140801000000_create_groupings.rb +11 -0
- data/spec/dummy/db/migrate/20150406171536_create_categories.rb +27 -0
- data/spec/dummy/db/migrate/20150408200916_create_loan_programs.rb +26 -0
- data/spec/dummy/db/migrate/20150408201429_create_types.rb +21 -0
- data/spec/dummy/db/migrate/20150420000001_create_heads.rb +14 -0
- data/spec/dummy/db/migrate/20150420000002_create_head_versions.rb +15 -0
- data/spec/dummy/db/migrate/20150420000003_create_grouping_head_versions.rb +12 -0
- data/spec/dummy/db/migrate/20151023000001_create_simple.rb +12 -0
- data/spec/dummy/db/seeds.rb +8 -0
- data/spec/dummy/delorean/blame_report.dl +171 -0
- data/spec/dummy/delorean/data_report.dl +105 -0
- data/spec/dummy/delorean/fields.dl +52 -0
- data/spec/dummy/delorean/styles.dl +134 -0
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/lib/class_list.rb +3 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/icons/READ.txt +3 -0
- data/spec/dummy/public/icons/application_cascade.png +0 -0
- data/spec/dummy/public/icons/application_delete.png +0 -0
- data/spec/dummy/public/icons/application_put.png +0 -0
- data/spec/dummy/public/icons/application_view_detail.png +0 -0
- data/spec/dummy/public/icons/arrow_in.png +0 -0
- data/spec/dummy/public/icons/arrow_refresh.png +0 -0
- data/spec/dummy/public/icons/database_save.png +0 -0
- data/spec/dummy/public/icons/door_in.png +0 -0
- data/spec/dummy/public/icons/door_out.png +0 -0
- data/spec/dummy/public/icons/group.png +0 -0
- data/spec/dummy/public/icons/page_lightning.png +0 -0
- data/spec/dummy/public/icons/printer.png +0 -0
- data/spec/dummy/public/icons/report_disk.png +0 -0
- data/spec/dummy/public/icons/report_go.png +0 -0
- data/spec/dummy/public/icons/report_magnify.png +0 -0
- data/spec/dummy/public/icons/script.png +0 -0
- data/spec/dummy/public/icons/script_add.png +0 -0
- data/spec/dummy/public/icons/script_go.png +0 -0
- data/spec/dummy/public/icons/script_key.png +0 -0
- data/spec/dummy/public/icons/table_go.png +0 -0
- data/spec/dummy/public/icons/time.png +0 -0
- data/spec/dummy/public/icons/time_add.png +0 -0
- data/spec/dummy/public/icons/time_go.png +0 -0
- data/spec/dummy/public/icons/timeline_marker.png +0 -0
- data/spec/dummy/public/icons/user_add.png +0 -0
- data/spec/dummy/public/icons/user_delete.png +0 -0
- data/spec/dummy/public/icons/user_edit.png +0 -0
- data/spec/dummy/public/icons/wrench.png +0 -0
- data/spec/dummy/script/delayed_job +6 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/features/javascripts/job_dashboard_live_search.js.coffee +8 -0
- data/spec/features/javascripts/login.js.coffee +8 -0
- data/spec/features/jobs_dashboard_netzke_spec.rb +24 -0
- data/spec/features/jobs_dashboard_spec.rb +49 -0
- data/spec/fixtures/scripts/load_tests/script1.dl +2 -0
- data/spec/fixtures/scripts/load_tests/script2.dl +2 -0
- data/spec/job_helper.rb +102 -0
- data/spec/lib/data_exporter_spec.rb +71 -0
- data/spec/lib/data_importer_spec.rb +461 -0
- data/spec/lib/xl_spec.rb +198 -0
- data/spec/lib/xl_styles_spec.rb +115 -0
- data/spec/models/api_auth_spec.rb +187 -0
- data/spec/models/posting_spec.rb +107 -0
- data/spec/models/promise_spec.rb +65 -0
- data/spec/models/script_spec.rb +187 -0
- data/spec/models/user_spec.rb +68 -0
- data/spec/requests/routes_spec.rb +12 -0
- data/spec/spec_helper.rb +61 -0
- data/spec/support/clean_db_helpers.rb +18 -0
- data/spec/support/delayed_job_helpers.rb +12 -0
- data/spec/support/user_helpers.rb +12 -0
- metadata +139 -89
- data/app/components/marty/auth_app.rb~ +0 -51
- data/app/components/marty/auth_app/javascripts/auth_app.js~ +0 -91
- data/app/components/marty/cm_form_panel.rb~ +0 -5
- data/app/components/marty/cm_grid_panel.rb~ +0 -35
- data/app/components/marty/data_import_view.rb~ +0 -142
- data/app/components/marty/extras/layout.rb~ +0 -46
- data/app/components/marty/live_search_grid_panel.rb~ +0 -49
- data/app/components/marty/main_auth_app.rb~ +0 -238
- data/app/components/marty/mcfly_grid_panel.rb~ +0 -80
- data/app/components/marty/new_posting_form.rb~ +0 -46
- data/app/components/marty/new_posting_window.rb~ +0 -21
- data/app/components/marty/pivot_grid.rb +0 -52
- data/app/components/marty/pivot_grid/endpoints.rb +0 -45
- data/app/components/marty/pivot_grid/javascripts/extensions.js +0 -150
- data/app/components/marty/pivot_grid/javascripts/pivot_grid.js +0 -86
- data/app/components/marty/pivot_grid/services.rb +0 -44
- data/app/components/marty/posting_grid.rb~ +0 -140
- data/app/components/marty/promise_view.rb~ +0 -157
- data/app/components/marty/promise_view/stylesheets/promise_view.css~ +0 -15
- data/app/components/marty/report_form.rb~ +0 -217
- data/app/components/marty/report_select.rb~ +0 -133
- data/app/components/marty/reporting.rb~ +0 -39
- data/app/components/marty/script_detail.rb~ +0 -430
- data/app/components/marty/script_form.rb~ +0 -233
- data/app/components/marty/script_form/javascripts/Ext.ux.form.field.CodeMirror.js~ +0 -909
- data/app/components/marty/script_grid.rb~ +0 -99
- data/app/components/marty/script_tester.rb~ +0 -213
- data/app/components/marty/scripting.rb~ +0 -124
- data/app/components/marty/select_report.rb~ +0 -143
- data/app/components/marty/simple_app.rb~ +0 -101
- data/app/components/marty/tag_grid.rb~ +0 -89
- data/app/components/marty/tree_panel.rb~ +0 -256
- data/app/components/marty/tree_panel/javascripts/tree_panel.js~ +0 -317
- data/app/components/marty/user_pivot.rb +0 -128
- data/app/components/marty/user_view.rb~ +0 -188
- data/app/controllers/marty/application_controller.rb~ +0 -133
- data/app/controllers/marty/components_controller.rb~ +0 -37
- data/app/controllers/marty/job_controller.rb~ +0 -28
- data/app/controllers/marty/rpc_controller.rb~ +0 -61
- data/app/helpers/marty/script_set.rb~ +0 -59
- data/app/models/marty/api_auth.rb~ +0 -48
- data/app/models/marty/data_change.rb~ +0 -141
- data/app/models/marty/enum.rb~ +0 -16
- data/app/models/marty/import_type.rb~ +0 -48
- data/app/models/marty/poop.rb~ +0 -169
- data/app/models/marty/posting.rb~ +0 -86
- data/app/models/marty/posting_type.rb~ +0 -21
- data/app/models/marty/promise.rb~ +0 -196
- data/app/models/marty/role.rb~ +0 -10
- data/app/models/marty/script.rb~ +0 -62
- data/app/models/marty/tag.rb~ +0 -91
- data/app/models/marty/user.rb~ +0 -148
- data/app/models/marty/user_role.rb~ +0 -13
- data/app/views/layouts/marty/application.html.erb~ +0 -11
- data/config/routes.rb~ +0 -10
- data/db/migrate/019_create_marty_postings.rb~ +0 -19
- data/db/migrate/095_create_marty_tags.rb~ +0 -19
- data/lib/marty.rb~ +0 -13
- data/lib/marty/content_handler.rb~ +0 -93
- data/lib/marty/data_exporter.rb~ +0 -137
- data/lib/marty/data_importer.rb~ +0 -114
- data/lib/marty/data_row_processor.rb~ +0 -206
- data/lib/marty/drop_folder_hook.rb~ +0 -17
- data/lib/marty/folder_hook.rb~ +0 -9
- data/lib/marty/lazy_column_loader.rb~ +0 -47
- data/lib/marty/mcfly_query.rb~ +0 -188
- data/lib/marty/migrations.rb~ +0 -65
- data/lib/marty/monkey.rb~ +0 -160
- data/lib/marty/permissions.rb~ +0 -69
- data/lib/marty/promise.rb~ +0 -41
- data/lib/marty/promise_job.rb~ +0 -121
- data/lib/marty/promise_proxy.rb~ +0 -69
- data/lib/marty/util.rb~ +0 -80
- data/lib/marty/version.rb~ +0 -3
- data/lib/marty/xl.rb~ +0 -526
- data/lib/pyxll/README.txt~ +0 -16
- data/lib/pyxll/gemini.py~ +0 -110
- data/lib/pyxll/pyxll.cfg~ +0 -12
data/lib/marty/permissions.rb~
DELETED
@@ -1,69 +0,0 @@
|
|
1
|
-
module Marty
|
2
|
-
module Permissions
|
3
|
-
# Make sure there are admin and user_manager roles,
|
4
|
-
# even if hosting app doesn't define them
|
5
|
-
REQ_ROLES = [:admin, :user_manager]
|
6
|
-
ALL_ROLES = (Set.new Rails.configuration.marty.roles.clone).merge(REQ_ROLES)
|
7
|
-
ACTIONS = Set.new [:create, :read, :update, :delete] # CRUD
|
8
|
-
|
9
|
-
# Call using following format
|
10
|
-
# has_marty_permissions create: [:dev, :admin],
|
11
|
-
# read: :any,
|
12
|
-
# update: :admin,
|
13
|
-
# delete: :none
|
14
|
-
#
|
15
|
-
# Allowed actions are only :create, :read, :update, :delete
|
16
|
-
# Roles can be passed in as a symbol or array of symbols
|
17
|
-
#
|
18
|
-
# :any gives permission to the action if user belongs to at least 1 role
|
19
|
-
# :none overrides any other roles
|
20
|
-
# e.g. delete: [:none, :admin] would return false when calling
|
21
|
-
# can_allow_action? even if the user belong to the admin role
|
22
|
-
def has_marty_permissions(attrs)
|
23
|
-
raise "bad attrs" unless attrs.is_a?(Hash)
|
24
|
-
raise "unknown action - only create/read/update/delete allowed" unless
|
25
|
-
attrs.keys.to_set.subset? ACTIONS
|
26
|
-
raise "unknown role" unless
|
27
|
-
attrs.values.flatten.to_set.subset? (ALL_ROLES << :any << :none)
|
28
|
-
|
29
|
-
#klass = self.to_s
|
30
|
-
#return "#{klass} with #{attrs.inspect}"
|
31
|
-
self.define_singleton_method(:marty_permissions) { attrs }
|
32
|
-
end
|
33
|
-
|
34
|
-
def current_user_roles
|
35
|
-
roles = Mcfly.whodunnit.roles rescue []
|
36
|
-
Set.new(roles.map {|r| r.name.to_sym})
|
37
|
-
end
|
38
|
-
|
39
|
-
def can_perform_action?(action)
|
40
|
-
roles = self.current_user_roles
|
41
|
-
roles << :any if self.has_any_perm?
|
42
|
-
|
43
|
-
allow = false
|
44
|
-
|
45
|
-
if self.respond_to?(:marty_permissions)
|
46
|
-
perms = self.marty_permissions
|
47
|
-
result = []
|
48
|
-
result = (result << perms.fetch(action.to_sym)).flatten if
|
49
|
-
perms.include?(action.to_sym)
|
50
|
-
|
51
|
-
result.each {|r| allow ||= roles.include? r} unless
|
52
|
-
result.include?(:none)
|
53
|
-
#puts "#{self.to_s} #{action} - #{allow}"
|
54
|
-
end
|
55
|
-
allow
|
56
|
-
end
|
57
|
-
|
58
|
-
# generate has_xxx_perm? methods for all permissions.
|
59
|
-
Rails.configuration.marty.roles.each { |role|
|
60
|
-
define_method("has_#{role}_perm?") do
|
61
|
-
current_user_roles.member? role
|
62
|
-
end
|
63
|
-
}
|
64
|
-
|
65
|
-
def has_any_perm?
|
66
|
-
!(current_user_roles & ALL_ROLES).empty?
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
data/lib/marty/promise.rb~
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
class Marty::Promise < Struct.new(:arg1)
|
2
|
-
def log(msg)
|
3
|
-
open('/tmp/dj.out', 'a') { |f|
|
4
|
-
f.puts msg
|
5
|
-
}
|
6
|
-
end
|
7
|
-
|
8
|
-
def perform
|
9
|
-
log "hello there #{Process.pid} #{arg1}"
|
10
|
-
|
11
|
-
raise "oops #{Process.pid}" if arg1 == 5
|
12
|
-
|
13
|
-
sleep 0.2
|
14
|
-
end
|
15
|
-
|
16
|
-
def error(job, exc)
|
17
|
-
log "Error #{Process.pid} #{job} #{exc}"
|
18
|
-
end
|
19
|
-
|
20
|
-
def failure(job)
|
21
|
-
log "failure #{Process.pid}, #{job}"
|
22
|
-
end
|
23
|
-
|
24
|
-
def before(job)
|
25
|
-
log "before #{Process.pid}, #{job}"
|
26
|
-
end
|
27
|
-
|
28
|
-
def after(job)
|
29
|
-
log "after #{Process.pid}, #{job}"
|
30
|
-
end
|
31
|
-
|
32
|
-
def success(job)
|
33
|
-
log "success #{Process.pid}, #{job}"
|
34
|
-
end
|
35
|
-
|
36
|
-
def max_attempts
|
37
|
-
return 1
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
|
data/lib/marty/promise_job.rb~
DELETED
@@ -1,121 +0,0 @@
|
|
1
|
-
require 'delorean_lang'
|
2
|
-
|
3
|
-
class Delorean::BaseModule::NodeCall
|
4
|
-
def initialize(_e, engine, node, params)
|
5
|
-
super
|
6
|
-
|
7
|
-
# If call has a promise_id (i.e. is from a promise) then that's
|
8
|
-
# our parent. Otherwise, we use its parent as our parent.
|
9
|
-
params[:_parent_id] = _e[:_promise_id] || _e[:_parent_id]
|
10
|
-
params[:_user_id] = _e[:_user_id] || Mcfly.whodunnit.try(:id)
|
11
|
-
end
|
12
|
-
|
13
|
-
# Monkey-patch '|' method for Delorean NodeCall to create promise
|
14
|
-
# jobs and return promise proxy objects.
|
15
|
-
def |(args)
|
16
|
-
if args.is_a?(String)
|
17
|
-
attr = args
|
18
|
-
args = [attr]
|
19
|
-
else
|
20
|
-
raise "bad arg to %" unless args.is_a?(Array)
|
21
|
-
attr = nil
|
22
|
-
end
|
23
|
-
|
24
|
-
script, tag = engine.module_name, engine.sset.tag
|
25
|
-
nn = node.is_a?(Class) ? node.name : node.to_s
|
26
|
-
|
27
|
-
begin
|
28
|
-
# make sure params is serialzable before starting a Job
|
29
|
-
Marshal.dump(params)
|
30
|
-
rescue => exc
|
31
|
-
raise "non-serializable parameters"
|
32
|
-
end
|
33
|
-
|
34
|
-
title = params["p_title"] || "#{script}::#{nn.demodulize}"
|
35
|
-
timeout = params["p_timeout"] || Marty::Promise::DEFAULT_PROMISE_TIMEOUT
|
36
|
-
hook = params["p_hook"]
|
37
|
-
promise = Marty::Promise.
|
38
|
-
create(title: title,
|
39
|
-
user_id: params[:_user_id],
|
40
|
-
parent_id: params[:_parent_id],
|
41
|
-
)
|
42
|
-
params[:_promise_id] = promise.id
|
43
|
-
|
44
|
-
begin
|
45
|
-
job = Delayed::Job.enqueue Marty::PromiseJob.
|
46
|
-
new(promise, title, script, tag, nn, params, args, hook)
|
47
|
-
rescue => exc
|
48
|
-
# log "CALLERR #{exc}"
|
49
|
-
res = Delorean::Engine.grok_runtime_exception(exc)
|
50
|
-
promise.set_start
|
51
|
-
promise.set_result(res)
|
52
|
-
# log "CALLERRSET #{res}"
|
53
|
-
raise
|
54
|
-
end
|
55
|
-
|
56
|
-
# keep a reference to the job. This is needed in case we want to
|
57
|
-
# work off a promise job that we're waiting for and which hasn't
|
58
|
-
# been reserved yet.
|
59
|
-
promise.job_id = job.id
|
60
|
-
promise.save!
|
61
|
-
|
62
|
-
Marty::PromiseProxy.new(promise.id, timeout, attr)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
class Delorean::Engine
|
67
|
-
def background_eval(node, params, attrs)
|
68
|
-
nc = Delorean::BaseModule::NodeCall.new({}, self, node, params)
|
69
|
-
# start the background promise
|
70
|
-
nc | attrs
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
class Marty::PromiseJob < Struct.new(:promise,
|
75
|
-
:title,
|
76
|
-
:sname,
|
77
|
-
:tag,
|
78
|
-
:node,
|
79
|
-
:params,
|
80
|
-
:attrs,
|
81
|
-
:hook,
|
82
|
-
)
|
83
|
-
# def log(msg)
|
84
|
-
# open('/tmp/dj.out', 'a') { |f| f.puts msg }
|
85
|
-
# end
|
86
|
-
|
87
|
-
def perform
|
88
|
-
# log "PERF #{Process.pid} #{title}"
|
89
|
-
|
90
|
-
promise.set_start
|
91
|
-
|
92
|
-
begin
|
93
|
-
# in case the job writes to the the database
|
94
|
-
Mcfly.whodunnit = promise.user
|
95
|
-
|
96
|
-
engine = Marty::ScriptSet.new(tag).get_engine(sname)
|
97
|
-
|
98
|
-
engine.evaluate_attrs(node, attrs, params)
|
99
|
-
|
100
|
-
res = attrs.each_with_object({}) { |attr, h|
|
101
|
-
h[attr] = engine.evaluate(node, attr, params)
|
102
|
-
}
|
103
|
-
|
104
|
-
# log "DONE #{Process.pid} #{promise.id} #{Time.now.to_f} #{res}"
|
105
|
-
rescue => exc
|
106
|
-
res = Delorean::Engine.grok_runtime_exception(exc)
|
107
|
-
# log "ERR- #{Process.pid} #{promise.id} #{Time.now.to_f} #{exc}"
|
108
|
-
end
|
109
|
-
promise.set_result(res)
|
110
|
-
|
111
|
-
begin
|
112
|
-
hook.run(res) if hook
|
113
|
-
rescue => exc
|
114
|
-
Marty::Util.logger.error "promise hook failed: #{exc}"
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
def max_attempts
|
119
|
-
1
|
120
|
-
end
|
121
|
-
end
|
data/lib/marty/promise_proxy.rb~
DELETED
@@ -1,69 +0,0 @@
|
|
1
|
-
# Promise mechanism shamelessly stolen and modified from
|
2
|
-
# https://github.com/bhuga/promising-future/blob/master/lib/promise.rb
|
3
|
-
|
4
|
-
class Marty::PromiseProxy < BasicObject
|
5
|
-
NOT_SET = ::Object.new.freeze
|
6
|
-
METH_SET = ::Set[:marshal_load, :marshal_dump, :force, :__force__]
|
7
|
-
|
8
|
-
instance_methods.each {|m| undef_method m unless m =~ /^(__.*|object_id)$/}
|
9
|
-
|
10
|
-
def initialize(promise_id, timeout, attr=nil)
|
11
|
-
marshal_load([promise_id, timeout, attr])
|
12
|
-
end
|
13
|
-
|
14
|
-
def marshal_dump
|
15
|
-
[@promise.id, @timeout, @attr]
|
16
|
-
end
|
17
|
-
|
18
|
-
def marshal_load(args)
|
19
|
-
promise_id, @timeout, @attr = args
|
20
|
-
@promise = ::Marty::Promise.find(promise_id)
|
21
|
-
@mutex = ::Mutex.new
|
22
|
-
@result = NOT_SET
|
23
|
-
end
|
24
|
-
|
25
|
-
def __promise_id__
|
26
|
-
@promise.id
|
27
|
-
end
|
28
|
-
|
29
|
-
##
|
30
|
-
# Force the evaluation of this promise immediately
|
31
|
-
#
|
32
|
-
# @return [Object]
|
33
|
-
def __force__
|
34
|
-
if @result.equal?(NOT_SET)
|
35
|
-
@mutex.synchronize do
|
36
|
-
if @result.equal?(NOT_SET)
|
37
|
-
begin
|
38
|
-
@result = @promise.wait_for_result(@timeout)
|
39
|
-
@result = @result[@attr] if @attr && !@result["error"]
|
40
|
-
rescue ::Exception => exc
|
41
|
-
@result = ::Delorean::Engine.grok_runtime_exception(exc)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
# FIXME: the logic for shape of exceptions from Delorean is spread
|
48
|
-
# all over the place.
|
49
|
-
@result.is_a?(::Hash) &&
|
50
|
-
@result["error"] ? ::Kernel.raise(@result["error"]) : @result
|
51
|
-
end
|
52
|
-
|
53
|
-
alias_method :force, :__force__
|
54
|
-
|
55
|
-
##
|
56
|
-
# Does this promise support the given method?
|
57
|
-
#
|
58
|
-
# @param [Symbol]
|
59
|
-
# @return [Boolean]
|
60
|
-
def respond_to?(method, include_all=false)
|
61
|
-
METH_SET.member?(method) || __force__.respond_to?(method, include_all)
|
62
|
-
end
|
63
|
-
|
64
|
-
private
|
65
|
-
|
66
|
-
def method_missing(method, *args, &block)
|
67
|
-
__force__.__send__(method, *args, &block)
|
68
|
-
end
|
69
|
-
end
|
data/lib/marty/util.rb~
DELETED
@@ -1,80 +0,0 @@
|
|
1
|
-
module Marty::Util
|
2
|
-
def self.set_posting_id(sid)
|
3
|
-
snap = Marty::Posting.find_by_id(sid)
|
4
|
-
sid = nil if snap && (snap.created_dt == Float::INFINITY)
|
5
|
-
Netzke::Base.session[:posting] = sid
|
6
|
-
end
|
7
|
-
|
8
|
-
def self.get_posting
|
9
|
-
sid = Netzke::Base.session && Netzke::Base.session[:posting]
|
10
|
-
return unless sid.is_a? Fixnum
|
11
|
-
sid && Marty::Posting.find_by_id(sid)
|
12
|
-
end
|
13
|
-
|
14
|
-
def self.get_posting_time
|
15
|
-
snap = self.get_posting
|
16
|
-
snap ? snap.created_dt : Float::INFINITY
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.warped?
|
20
|
-
self.get_posting_time != Float::INFINITY
|
21
|
-
end
|
22
|
-
|
23
|
-
def self.logger
|
24
|
-
@@s_logger ||= Rails.logger || Logger.new($stderr)
|
25
|
-
end
|
26
|
-
|
27
|
-
# route path to where Marty is mounted
|
28
|
-
def self.marty_path
|
29
|
-
Rails.application.routes.named_routes[:marty].path.spec
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.pg_range_to_human(r)
|
33
|
-
return r if r == "empty" || r.nil?
|
34
|
-
|
35
|
-
m = /\A(?<open>\[|\()(?<start>.*?),(?<end>.*?)(?<close>\]|\))\z/.match(r)
|
36
|
-
|
37
|
-
raise "bad PG range #{r}" unless m
|
38
|
-
|
39
|
-
if m[:start] == ""
|
40
|
-
res = ""
|
41
|
-
else
|
42
|
-
op = m[:open] == "(" ? ">" : ">="
|
43
|
-
res = "#{op}#{m[:start]}"
|
44
|
-
end
|
45
|
-
|
46
|
-
if m[:end] != ""
|
47
|
-
op = m[:close] == ")" ? "<" : "<="
|
48
|
-
res += "#{op}#{m[:end]}"
|
49
|
-
end
|
50
|
-
|
51
|
-
res
|
52
|
-
end
|
53
|
-
|
54
|
-
def self.human_to_pg_range(r)
|
55
|
-
return r if r == "empty"
|
56
|
-
|
57
|
-
m = /\A
|
58
|
-
((?<op0>\>|\>=)(?<start>[^\<\>\=]*?))?
|
59
|
-
((?<op1>\<|\<=)(?<end>[^\<\>\=]*?))?
|
60
|
-
\z/x.match(r)
|
61
|
-
|
62
|
-
raise "bad range #{r}" unless m
|
63
|
-
|
64
|
-
if m[:op0]
|
65
|
-
open = m[:op0] == ">" ? "(" : "["
|
66
|
-
start = "#{open}#{m[:start]}"
|
67
|
-
else
|
68
|
-
start = "["
|
69
|
-
end
|
70
|
-
|
71
|
-
if m[:op1]
|
72
|
-
close = m[:op1] == "<" ? ")" : "]"
|
73
|
-
ends = "#{m[:end]}#{close}"
|
74
|
-
else
|
75
|
-
ends = "]"
|
76
|
-
end
|
77
|
-
|
78
|
-
"#{start},#{ends}"
|
79
|
-
end
|
80
|
-
end
|
data/lib/marty/version.rb~
DELETED
data/lib/marty/xl.rb~
DELETED
@@ -1,526 +0,0 @@
|
|
1
|
-
require 'axlsx'
|
2
|
-
require 'delorean_lang'
|
3
|
-
|
4
|
-
class Marty::Xl
|
5
|
-
include Delorean::Model
|
6
|
-
|
7
|
-
def self.spreadsheet(worksheets)
|
8
|
-
xl = Marty::Xl.new(worksheets)
|
9
|
-
xl.package
|
10
|
-
end
|
11
|
-
|
12
|
-
def deep_copy(value)
|
13
|
-
return value.each_with_object({}){|(k, v), h| h[k] = deep_copy(v)} if
|
14
|
-
value.is_a?(Hash)
|
15
|
-
|
16
|
-
value.is_a?(Array) ? value.map{|v| deep_copy(v)} : value
|
17
|
-
end
|
18
|
-
|
19
|
-
def merge_cell_edges(a, b)
|
20
|
-
return b unless a.kind_of?(Hash) && b.kind_of?(Hash)
|
21
|
-
|
22
|
-
a_border, b_border = a[:border], b[:border]
|
23
|
-
|
24
|
-
return b unless a_border.is_a?(Hash) && a_border[:edges].is_a?(Array)
|
25
|
-
|
26
|
-
non_match = a_border.detect {
|
27
|
-
|key, value|
|
28
|
-
key != :edges && b_border[key] != value
|
29
|
-
}
|
30
|
-
|
31
|
-
a_border[:edges].each do |edge|
|
32
|
-
unless b_border[:edges].include? edge
|
33
|
-
# add new edges:
|
34
|
-
b_border[:edges] << edge
|
35
|
-
|
36
|
-
# add new style/color for the new edge if there is no style
|
37
|
-
# match with the old edges:
|
38
|
-
b["border_#{edge}".to_sym] = a_border.each_with_object({}) {
|
39
|
-
|(key, value), h|
|
40
|
-
h[key] = value unless key == :edges
|
41
|
-
} if non_match
|
42
|
-
end
|
43
|
-
end
|
44
|
-
b
|
45
|
-
end
|
46
|
-
|
47
|
-
def merge_row_edges(a, b)
|
48
|
-
return b unless a.count > 0
|
49
|
-
a.each_index do |ind|
|
50
|
-
b[ind] = merge_cell_edges(a[ind], deep_copy(b[ind]))
|
51
|
-
end
|
52
|
-
b
|
53
|
-
end
|
54
|
-
|
55
|
-
def bordered_cells(a, b)
|
56
|
-
a.each_index do |c|
|
57
|
-
a[c] = merge_cell_edges(b[c], deep_copy(a[c])) if a[c] && b[c]
|
58
|
-
b[c] = a[c] unless a[c].nil?
|
59
|
-
end
|
60
|
-
b.each_index { |el| b[el] = {} unless b[el] }
|
61
|
-
end
|
62
|
-
|
63
|
-
def position_row(d, column_offset, r_number, rows, styles, row_styles)
|
64
|
-
rows[r_number] ||= []
|
65
|
-
styles[r_number] ||= []
|
66
|
-
row_styles[r_number] ||= {}
|
67
|
-
|
68
|
-
new_row, new_style = rows[r_number], styles[r_number]
|
69
|
-
|
70
|
-
(0...column_offset).each do |t|
|
71
|
-
new_row[t] ||= ""
|
72
|
-
new_style[t] ||= {}
|
73
|
-
end
|
74
|
-
|
75
|
-
d[1].each_index do |c_index|
|
76
|
-
new_row[c_index+column_offset] = d[1][c_index]
|
77
|
-
end if d[1].kind_of?(Array)
|
78
|
-
|
79
|
-
if (d.length > 2) && d[2].kind_of?(Hash) && d[2]["style"].kind_of?(Array)
|
80
|
-
d[2]["style"].each_index do |c_index|
|
81
|
-
new_style[c_index+column_offset] = d[2]["style"][c_index]
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
# apply style for the row as a whole:
|
86
|
-
if (d.length > 2) && d[2].kind_of?(Hash)
|
87
|
-
d[2].each do |key, value|
|
88
|
-
unless key == :style.to_s
|
89
|
-
row_styles[r_number][key] = value
|
90
|
-
else
|
91
|
-
# skip if the style is an array: /style as an array is
|
92
|
-
# handled by the 'apply a style to each cell' section/
|
93
|
-
next unless value.kind_of?(Hash)
|
94
|
-
d[1].length.times do |t|
|
95
|
-
new_style[t+column_offset] = value
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
# row data, style:
|
101
|
-
rows[r_number], styles[r_number] = new_row, new_style
|
102
|
-
end
|
103
|
-
|
104
|
-
def position_elem(d, offset, last_row, el)
|
105
|
-
x1, y1, x2, y2, w, h = d[1]
|
106
|
-
column_offset, row_offset = offset
|
107
|
-
|
108
|
-
y_coords = y2.is_a?(Fixnum) || d[0] != "image" ? [y1, y2] : [y1]
|
109
|
-
x_coords = x2.is_a?(Fixnum) || d[0] != "image" ? [x1, x2] : [x1]
|
110
|
-
|
111
|
-
# add the row offset:
|
112
|
-
y1, y2 = y_coords.map { |y|
|
113
|
-
if y.is_a?(Fixnum)
|
114
|
-
row_offset + y
|
115
|
-
elsif y.is_a?(Hash) && y["off"].is_a?(Fixnum)
|
116
|
-
last_row + y["off"]
|
117
|
-
else
|
118
|
-
raise "bad offset #{y}"
|
119
|
-
end
|
120
|
-
}
|
121
|
-
|
122
|
-
# add the column offset:
|
123
|
-
x1, x2 = x_coords.map { |x|
|
124
|
-
raise "bad range point #{x}" unless x.is_a? Fixnum
|
125
|
-
column_offset + x
|
126
|
-
}
|
127
|
-
|
128
|
-
el[last_row] = [] unless
|
129
|
-
el[last_row] || ["border", "image"].member?(d[0])
|
130
|
-
|
131
|
-
case d[0]
|
132
|
-
when "conditional_formatting"
|
133
|
-
el[last_row] << [d[0], [x1, y1, x2, y2], d[2]]
|
134
|
-
when "merge"
|
135
|
-
el[last_row] << [d[0], [x1, y1, x2, y2]]
|
136
|
-
when "border"
|
137
|
-
el << [d[0], [x1, y1, x2, y2], d[2]]
|
138
|
-
when "image"
|
139
|
-
el << [d[0], [x1, y1, x2, y2, w, h], d[2]]
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
def position_borders(borders)
|
144
|
-
b_styles = []
|
145
|
-
borders.each do |b|
|
146
|
-
top_row, middle_row, bottom_row, edge_h = [], [], [], {}
|
147
|
-
br, range, defaults = b
|
148
|
-
col0, row0, colw, rowh = range
|
149
|
-
|
150
|
-
raise "wrong border range #{range}" if
|
151
|
-
col0 > colw || row0 > rowh || (col0 == colw && row0 == rowh)
|
152
|
-
|
153
|
-
defaults = self.class.symbolize_keys(defaults, ':')
|
154
|
-
|
155
|
-
boxborders = Hash.new do |hash, key|
|
156
|
-
hash[key] = {
|
157
|
-
border: defaults.merge( {edges: key.to_s.split('_').map(&:to_sym)} )
|
158
|
-
}
|
159
|
-
end
|
160
|
-
|
161
|
-
boxborders[:nil] = {}
|
162
|
-
|
163
|
-
tro, mro, bro =
|
164
|
-
top_row.object_id, middle_row.object_id, bottom_row.object_id
|
165
|
-
|
166
|
-
if col0 == colw
|
167
|
-
# vertical line
|
168
|
-
edge_h[tro], edge_h[mro], edge_h[bro] =
|
169
|
-
["left"], ["left"], ["left"]
|
170
|
-
elsif row0 == rowh
|
171
|
-
# horizontal line
|
172
|
-
edge_h[tro], edge_h[mro], edge_h[bro] =
|
173
|
-
["top"]*3, ["top"]*3, ["top"]*3
|
174
|
-
else
|
175
|
-
# box
|
176
|
-
edge_h[tro] = ["top_left", "top_right", "top"]
|
177
|
-
edge_h[mro] = ["left", "right", "nil"]
|
178
|
-
edge_h[bro] = ["bottom_left", "bottom_right", "bottom"]
|
179
|
-
end
|
180
|
-
|
181
|
-
[top_row, middle_row, bottom_row].each do |r|
|
182
|
-
if col0 == colw
|
183
|
-
r[col0] = boxborders[edge_h[r.object_id][0].to_sym]
|
184
|
-
else
|
185
|
-
(col0...colw).each do |counter|
|
186
|
-
a = (counter == col0) ? boxborders[edge_h[r.object_id][0].to_sym] : {}
|
187
|
-
|
188
|
-
# counter == col0 == (colw - 1) => merge the edges:
|
189
|
-
a = boxborders[edge_h[r.object_id][1].to_sym] =
|
190
|
-
merge_cell_edges(a, deep_copy(boxborders[edge_h[r.object_id][1].to_sym])) if
|
191
|
-
counter == (colw - 1)
|
192
|
-
|
193
|
-
a = boxborders[edge_h[r.object_id][2].to_sym] unless
|
194
|
-
counter == col0 || counter == (colw - 1)
|
195
|
-
|
196
|
-
r[counter] = a
|
197
|
-
end
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
if row0 == rowh
|
202
|
-
b_styles[row0] ||= []
|
203
|
-
bordered_cells(top_row, b_styles[row0])
|
204
|
-
else
|
205
|
-
(row0...rowh).each_with_index do |r, i|
|
206
|
-
b_styles[r] ||= []
|
207
|
-
|
208
|
-
a = i == 0 ? top_row : []
|
209
|
-
|
210
|
-
a = merge_row_edges(a,bottom_row) if
|
211
|
-
i == (rowh - row0 - 1)
|
212
|
-
|
213
|
-
a = middle_row unless
|
214
|
-
i == 0 || i == (rowh - row0 - 1)
|
215
|
-
|
216
|
-
bordered_cells(a, b_styles[r])
|
217
|
-
end
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
b_styles
|
222
|
-
end
|
223
|
-
|
224
|
-
def worksheet_rows(ws, rows, styles, row_styles, format, borders, images)
|
225
|
-
wsrows = []
|
226
|
-
|
227
|
-
b_styles = position_borders(borders)
|
228
|
-
rlenmax = rows.map { |el| el.kind_of?(Array) ? el.length : 0 }.max
|
229
|
-
|
230
|
-
rows.each_with_index do |r, index|
|
231
|
-
rlen = r.kind_of?(Array) ? r.length : 0
|
232
|
-
|
233
|
-
if rlen < rlenmax
|
234
|
-
rows[index] ||= []
|
235
|
-
rows[index] += [""] * (rlenmax-rlen)
|
236
|
-
end
|
237
|
-
|
238
|
-
row_styles[index] ||= {}
|
239
|
-
|
240
|
-
rsi = row_styles[index]
|
241
|
-
|
242
|
-
rsi["style"] = styles[index].kind_of?(Array) ? styles[index] : []
|
243
|
-
|
244
|
-
if b_styles[index].kind_of?(Array) && b_styles[index].count > 0
|
245
|
-
len = [rsi["style"].count, b_styles[index].count].max
|
246
|
-
|
247
|
-
len.times do |ind|
|
248
|
-
b_styles[index][ind] ||= {}
|
249
|
-
rsi["style"][ind] ||= {}
|
250
|
-
|
251
|
-
rsi["style"][ind] =
|
252
|
-
rsi["style"][ind].merge(b_styles[index][ind])
|
253
|
-
end
|
254
|
-
|
255
|
-
rsi["style"] = rsi["style"].map{ |x| x || {} }
|
256
|
-
end
|
257
|
-
|
258
|
-
wsrows << ["row", rows[index], rsi]
|
259
|
-
|
260
|
-
if format[index] && format[index].kind_of?(Array)
|
261
|
-
format[index].each do |f|
|
262
|
-
raise "wrong number of arguments for #{f[0]}" unless
|
263
|
-
[
|
264
|
-
["conditional_formatting", 3],
|
265
|
-
["merge", 2]
|
266
|
-
].member?([f[0], f.length])
|
267
|
-
|
268
|
-
wsrows << f
|
269
|
-
end
|
270
|
-
end
|
271
|
-
|
272
|
-
end
|
273
|
-
|
274
|
-
apply_relative_worksheet_ops(ws, wsrows + images)
|
275
|
-
end
|
276
|
-
|
277
|
-
attr_reader :styles, :package
|
278
|
-
|
279
|
-
def initialize(worksheets)
|
280
|
-
@styles = {}
|
281
|
-
@package = Axlsx::Package.new
|
282
|
-
wb = package.workbook
|
283
|
-
|
284
|
-
# We got some sort of error if the worksheets is an array
|
285
|
-
if worksheets.is_a? Hash
|
286
|
-
ws = wb.add_worksheet(name: "EXCEPTION")
|
287
|
-
ws.add_row ["error", worksheets["error"]]
|
288
|
-
ws.add_row ["backtrace", worksheets["backtrace"]]
|
289
|
-
return
|
290
|
-
end
|
291
|
-
|
292
|
-
raise "expected worksheets array, got: #{worksheets}" unless
|
293
|
-
worksheets.is_a?(Array)
|
294
|
-
|
295
|
-
worksheets << ["No data", []] if worksheets.count == 0
|
296
|
-
|
297
|
-
worksheets.each { |opl|
|
298
|
-
name, ops, opts = opl
|
299
|
-
|
300
|
-
raise "bad worksheet name: #{name}" unless name.is_a?(String)
|
301
|
-
raise "bad worksheet ops: #{ops.inspect}" unless ops.is_a?(Array)
|
302
|
-
raise "bad options #{opts}" unless opts.is_a?(Hash) || opts.nil?
|
303
|
-
|
304
|
-
# Remove special characters and truncate sheet name due to Excel
|
305
|
-
# limitations. The following chars are not allowed: []*?:\/
|
306
|
-
name = name.gsub(/[\[\]\*\?\/\\]/, '_').gsub(':', '').truncate(31)
|
307
|
-
|
308
|
-
opts = self.class.symbolize_keys(opts || {}, ':')
|
309
|
-
widths = opts[:widths] || []
|
310
|
-
gridlines = opts[:gridlines] != 0
|
311
|
-
|
312
|
-
ws = wb.add_worksheet(name: name)
|
313
|
-
|
314
|
-
ws.column_widths(*widths) if widths.is_a?(Array) && widths.count > 0
|
315
|
-
ws.sheet_view.show_grid_lines = gridlines
|
316
|
-
|
317
|
-
apply_relative_worksheet_ops(ws, ops)
|
318
|
-
}
|
319
|
-
@package.use_shared_strings = true
|
320
|
-
end
|
321
|
-
|
322
|
-
def add_style(style)
|
323
|
-
raise "bad style" unless style.is_a?(Hash) || style.is_a?(Array)
|
324
|
-
|
325
|
-
if style.is_a?(Array)
|
326
|
-
style.map { |s|
|
327
|
-
styles[s] ||= package.workbook.styles.add_style(s)
|
328
|
-
}
|
329
|
-
else
|
330
|
-
styles[style] ||= package.workbook.styles.add_style(style)
|
331
|
-
end
|
332
|
-
end
|
333
|
-
|
334
|
-
def intern_range(ws, range)
|
335
|
-
return range if range.is_a? String
|
336
|
-
raise "bad range #{range}" unless range.is_a?(Array) && range.length==4
|
337
|
-
x1, y1, x2, y2 = range
|
338
|
-
|
339
|
-
y1, y2 = [y1, y2].map { |y|
|
340
|
-
next y unless y.is_a?(Hash)
|
341
|
-
raise "bad offset #{y}" unless y["off"].is_a?(Fixnum)
|
342
|
-
ws.rows.last.index + y["off"]
|
343
|
-
}
|
344
|
-
|
345
|
-
[x1, y1, x2, y2].each { |x|
|
346
|
-
raise "bad range point #{x}" unless x.is_a? Fixnum
|
347
|
-
}
|
348
|
-
Axlsx.cell_r(x1, y1) + ":" + Axlsx.cell_r(x2, y2)
|
349
|
-
end
|
350
|
-
|
351
|
-
def recalc_offsets(ops_pos)
|
352
|
-
new_ops1, new_ops2, new_ops = [], [], []
|
353
|
-
# precalculate the offsets of pos options embedded in another pos opt:
|
354
|
-
ops_pos.each { |d|
|
355
|
-
new_ops1 += d[2].select { |inner_ops|
|
356
|
-
inner_ops if inner_ops[0] == "pos"
|
357
|
-
}.map { |inner|
|
358
|
-
[inner[0], d[1].zip(inner[1]).map { |x,y| x+y }, inner[2] ]
|
359
|
-
}
|
360
|
-
}
|
361
|
-
# keep the offsets of non-pos options embedded in pos opt:
|
362
|
-
new_ops2 = ops_pos.map { |d|
|
363
|
-
[ d[0], d[1], d[2].select{|inner| inner if inner[0] != "pos" } ]
|
364
|
-
}
|
365
|
-
new_ops = new_ops1 + new_ops2
|
366
|
-
count = new_ops.select { |d|
|
367
|
-
d[2].select { |inner_ops|
|
368
|
-
inner_ops if inner_ops[0] == "pos"
|
369
|
-
}.count > 0
|
370
|
-
}.count
|
371
|
-
|
372
|
-
count == 0 ? new_ops.sort : recalc_offsets(new_ops)
|
373
|
-
end
|
374
|
-
|
375
|
-
def apply_relative_worksheet_ops(ws, ops)
|
376
|
-
|
377
|
-
non_pos = ops.select {|opl| opl[0] != "pos" }
|
378
|
-
ops_pos = ops.select {|opl| opl[0] == "pos" }
|
379
|
-
ops_brd = ops.select {|opl| opl[0] == "border" }
|
380
|
-
|
381
|
-
if (ops_pos.count > 0)
|
382
|
-
# Wrap all non-pos options in a pos option with offset 0, 0:
|
383
|
-
pos_00_ops = non_pos.count > 0 ? [ ["pos", [0, 0], non_pos] ] : []
|
384
|
-
# Recalculate the offsets of embedded pos opts:
|
385
|
-
ops = pos_00_ops + recalc_offsets(ops_pos)
|
386
|
-
elsif (ops_brd.count > 0)
|
387
|
-
# Wrap the non-pos options in a pos opt with offset 0, 0:
|
388
|
-
pos_00_ops = [ ["pos", [0, 0], non_pos] ]
|
389
|
-
ops = pos_00_ops
|
390
|
-
end
|
391
|
-
|
392
|
-
rows, styles, row_styles, format, borders, images = [], [], [], [], [], []
|
393
|
-
ops.each { |opl|
|
394
|
-
raise "bad op #{opl}" unless opl.length > 1
|
395
|
-
case opl[0]
|
396
|
-
when "pos"
|
397
|
-
op, offset, data = opl
|
398
|
-
raise "bad offset #{offset}" unless
|
399
|
-
offset.is_a?(Array) && offset.length == 2 &&
|
400
|
-
offset.all? {|x| x.is_a? Fixnum}
|
401
|
-
# column offset, row offset:
|
402
|
-
column_offset, row_offset = offset
|
403
|
-
r_number, last_row = row_offset, row_offset
|
404
|
-
|
405
|
-
data.each do |d|
|
406
|
-
raise "non array data #{d[1]}" unless d[1].is_a?(Array)
|
407
|
-
raise "non hash data options #{d[2]}" unless
|
408
|
-
[NilClass, Hash, String, Array].member? d[2].class
|
409
|
-
case d[0]
|
410
|
-
when "row"
|
411
|
-
position_row(d, column_offset, r_number, rows, styles, row_styles)
|
412
|
-
last_row = r_number
|
413
|
-
r_number += 1
|
414
|
-
when "conditional_formatting", "merge"
|
415
|
-
position_elem(d, offset, last_row, format)
|
416
|
-
when "border"
|
417
|
-
position_elem(d, offset, last_row, borders)
|
418
|
-
when "image"
|
419
|
-
position_elem(d, offset, last_row, images)
|
420
|
-
else
|
421
|
-
raise "unknown op #{d[0]} embedded in 'position' option"
|
422
|
-
end
|
423
|
-
end
|
424
|
-
|
425
|
-
when "row"
|
426
|
-
op, data, options = opl
|
427
|
-
|
428
|
-
raise "bad row op #{opl}" unless data.is_a?(Array) || opl.length > 3
|
429
|
-
raise "non hash options #{options} for row" unless
|
430
|
-
options.nil? || options.is_a?(Hash)
|
431
|
-
|
432
|
-
options = self.class.symbolize_keys(options || {}, ':')
|
433
|
-
|
434
|
-
options[:style] = add_style(options[:style]) if options[:style]
|
435
|
-
|
436
|
-
ws.add_row data, options
|
437
|
-
|
438
|
-
when "row_style"
|
439
|
-
op, row_num, style = opl
|
440
|
-
|
441
|
-
# FIXME: need to handle Array?
|
442
|
-
raise "non hash arg for row_style" unless style.is_a?(Hash)
|
443
|
-
raise "bad row num #{opl}" unless row_num.is_a?(Fixnum)
|
444
|
-
|
445
|
-
style = self.class.symbolize_keys(style, ':')
|
446
|
-
|
447
|
-
style_id = add_style(style)
|
448
|
-
row = ws.rows[row_num]
|
449
|
-
|
450
|
-
row.style = style_id
|
451
|
-
|
452
|
-
when "merge"
|
453
|
-
op, range = opl
|
454
|
-
|
455
|
-
raise "bad merge op #{opl}" unless opl.length == 2
|
456
|
-
range = intern_range(ws, range)
|
457
|
-
|
458
|
-
ws.merge_cells range
|
459
|
-
when "conditional_formatting"
|
460
|
-
op, range, format = opl
|
461
|
-
|
462
|
-
raise "non hash arg for format" unless format.is_a?(Hash)
|
463
|
-
|
464
|
-
range = intern_range(ws, range)
|
465
|
-
|
466
|
-
format = self.class.symbolize_keys(format, ':')
|
467
|
-
|
468
|
-
color_scale_a = format[:color_scale]
|
469
|
-
|
470
|
-
if color_scale_a
|
471
|
-
raise "color_scale must be an array" unless
|
472
|
-
color_scale_a.is_a?(Array)
|
473
|
-
|
474
|
-
raise "non-hash color_scale element" unless
|
475
|
-
color_scale_a.all? {|x| x.is_a?(Hash)}
|
476
|
-
|
477
|
-
format[:color_scale] = Axlsx::ColorScale.new(*color_scale_a)
|
478
|
-
end
|
479
|
-
|
480
|
-
dxfid = format[:dxfId]
|
481
|
-
format[:dxfId] = add_style(dxfid) if dxfid.is_a?(Hash)
|
482
|
-
|
483
|
-
ws.add_conditional_formatting(range, format)
|
484
|
-
when "image"
|
485
|
-
op, range, img = opl
|
486
|
-
raise "bad image params #{range}" unless
|
487
|
-
range.is_a?(Array) && range.length == 6
|
488
|
-
x1, y1, x2, y2, w, h = range
|
489
|
-
raise "bad image range, width or height #{range}" unless
|
490
|
-
[ x1, y1, w, h ].all? {|x| x.is_a? Fixnum}
|
491
|
-
ws.add_image(image_src: "#{Rails.public_path}/images/#{img}",
|
492
|
-
noSelect: true,
|
493
|
-
noMove: true) do |image|
|
494
|
-
image.width = w
|
495
|
-
image.height = h
|
496
|
-
image.start_at x1, y1
|
497
|
-
image.end_at x2, y2 if x2.is_a?(Fixnum) && y2.is_a?(Fixnum)
|
498
|
-
end
|
499
|
-
else
|
500
|
-
raise "unknown op #{opl[0]}"
|
501
|
-
end
|
502
|
-
}
|
503
|
-
worksheet_rows(ws,rows,styles,row_styles,format,borders,images) unless
|
504
|
-
[ops_pos.count, ops_brd.count].all?{ |a| a == 0 }
|
505
|
-
end
|
506
|
-
|
507
|
-
# recursive symbolize_keys. FIXME: this belongs in a generic
|
508
|
-
# library somewhere.
|
509
|
-
def self.symbolize_keys(obj, sym_str=nil)
|
510
|
-
case obj
|
511
|
-
when Array
|
512
|
-
obj.map {|x| symbolize_keys(x, sym_str)}
|
513
|
-
when Hash
|
514
|
-
obj.inject({}) { |result, (key, value)|
|
515
|
-
key = key.to_sym if key.is_a?(String)
|
516
|
-
result[key] = symbolize_keys(value, sym_str)
|
517
|
-
result
|
518
|
-
}
|
519
|
-
when String
|
520
|
-
(sym_str && obj.starts_with?(sym_str)) ? obj[sym_str.length..-1].to_sym : obj
|
521
|
-
else
|
522
|
-
obj
|
523
|
-
end
|
524
|
-
end
|
525
|
-
|
526
|
-
end
|