marty 0.5.15 → 0.5.16

Sign up to get free protection for your applications and to get access to all the features.
Files changed (212) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +27 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +23 -0
  5. data/Gemfile +23 -0
  6. data/INDEPENDENCE_ISSUES.md +23 -0
  7. data/app/assets/images/marty/.gitkeep +0 -0
  8. data/app/components/marty/report_form.rb +9 -4
  9. data/gemini_deprecations.md +6 -0
  10. data/lib/marty/data_change.rb +99 -0
  11. data/lib/marty/data_conversion.rb +11 -3
  12. data/lib/marty/data_exporter.rb +9 -0
  13. data/lib/marty/version.rb +1 -1
  14. data/marty.gemspec +35 -0
  15. data/script/rails +8 -0
  16. data/spec/controllers/application_controller_spec.rb +52 -0
  17. data/spec/controllers/job_controller_spec.rb +226 -0
  18. data/spec/controllers/rpc_controller_spec.rb +379 -0
  19. data/spec/controllers/rpc_import_spec.rb +45 -0
  20. data/spec/dummy/README.rdoc +261 -0
  21. data/spec/dummy/Rakefile +7 -0
  22. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  23. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  24. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  25. data/spec/dummy/app/controllers/components_controller.rb +7 -0
  26. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  27. data/spec/dummy/app/mailers/.gitkeep +0 -0
  28. data/spec/dummy/app/models/.gitkeep +0 -0
  29. data/spec/dummy/app/models/gemini/amortization_type.rb +5 -0
  30. data/spec/dummy/app/models/gemini/bud_category.rb +7 -0
  31. data/spec/dummy/app/models/gemini/entity.rb +2 -0
  32. data/spec/dummy/app/models/gemini/extras/data_import.rb +5 -0
  33. data/spec/dummy/app/models/gemini/extras/settlement_import.rb +28 -0
  34. data/spec/dummy/app/models/gemini/fannie_bup.rb +29 -0
  35. data/spec/dummy/app/models/gemini/grouping.rb +8 -0
  36. data/spec/dummy/app/models/gemini/grouping_head_version.rb +14 -0
  37. data/spec/dummy/app/models/gemini/head.rb +7 -0
  38. data/spec/dummy/app/models/gemini/head_version.rb +14 -0
  39. data/spec/dummy/app/models/gemini/helper.rb +44 -0
  40. data/spec/dummy/app/models/gemini/loan_program.rb +11 -0
  41. data/spec/dummy/app/models/gemini/mortgage_type.rb +5 -0
  42. data/spec/dummy/app/models/gemini/simple.rb +6 -0
  43. data/spec/dummy/app/models/gemini/streamline_type.rb +16 -0
  44. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  45. data/spec/dummy/config.ru +4 -0
  46. data/spec/dummy/config/application.rb +82 -0
  47. data/spec/dummy/config/boot.rb +10 -0
  48. data/spec/dummy/config/database.yml.example +10 -0
  49. data/spec/dummy/config/environment.rb +5 -0
  50. data/spec/dummy/config/environments/development.rb +35 -0
  51. data/spec/dummy/config/environments/production.rb +69 -0
  52. data/spec/dummy/config/environments/test.rb +39 -0
  53. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  54. data/spec/dummy/config/initializers/delayed_job.rb +5 -0
  55. data/spec/dummy/config/initializers/inflections.rb +15 -0
  56. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  57. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  58. data/spec/dummy/config/initializers/session_store.rb +8 -0
  59. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  60. data/spec/dummy/config/locales/en.yml +5 -0
  61. data/spec/dummy/config/routes.rb +12 -0
  62. data/spec/dummy/db/migrate/20140801000000_create_groupings.rb +11 -0
  63. data/spec/dummy/db/migrate/20150406171536_create_categories.rb +27 -0
  64. data/spec/dummy/db/migrate/20150408200916_create_loan_programs.rb +26 -0
  65. data/spec/dummy/db/migrate/20150408201429_create_types.rb +21 -0
  66. data/spec/dummy/db/migrate/20150420000001_create_heads.rb +14 -0
  67. data/spec/dummy/db/migrate/20150420000002_create_head_versions.rb +15 -0
  68. data/spec/dummy/db/migrate/20150420000003_create_grouping_head_versions.rb +12 -0
  69. data/spec/dummy/db/migrate/20151023000001_create_simple.rb +12 -0
  70. data/spec/dummy/db/seeds.rb +8 -0
  71. data/spec/dummy/delorean/blame_report.dl +171 -0
  72. data/spec/dummy/delorean/data_report.dl +105 -0
  73. data/spec/dummy/delorean/fields.dl +52 -0
  74. data/spec/dummy/delorean/styles.dl +134 -0
  75. data/spec/dummy/lib/assets/.gitkeep +0 -0
  76. data/spec/dummy/lib/class_list.rb +3 -0
  77. data/spec/dummy/log/.gitkeep +0 -0
  78. data/spec/dummy/public/404.html +26 -0
  79. data/spec/dummy/public/422.html +26 -0
  80. data/spec/dummy/public/500.html +25 -0
  81. data/spec/dummy/public/favicon.ico +0 -0
  82. data/spec/dummy/public/icons/READ.txt +3 -0
  83. data/spec/dummy/public/icons/application_cascade.png +0 -0
  84. data/spec/dummy/public/icons/application_delete.png +0 -0
  85. data/spec/dummy/public/icons/application_put.png +0 -0
  86. data/spec/dummy/public/icons/application_view_detail.png +0 -0
  87. data/spec/dummy/public/icons/arrow_in.png +0 -0
  88. data/spec/dummy/public/icons/arrow_refresh.png +0 -0
  89. data/spec/dummy/public/icons/database_save.png +0 -0
  90. data/spec/dummy/public/icons/door_in.png +0 -0
  91. data/spec/dummy/public/icons/door_out.png +0 -0
  92. data/spec/dummy/public/icons/group.png +0 -0
  93. data/spec/dummy/public/icons/page_lightning.png +0 -0
  94. data/spec/dummy/public/icons/printer.png +0 -0
  95. data/spec/dummy/public/icons/report_disk.png +0 -0
  96. data/spec/dummy/public/icons/report_go.png +0 -0
  97. data/spec/dummy/public/icons/report_magnify.png +0 -0
  98. data/spec/dummy/public/icons/script.png +0 -0
  99. data/spec/dummy/public/icons/script_add.png +0 -0
  100. data/spec/dummy/public/icons/script_go.png +0 -0
  101. data/spec/dummy/public/icons/script_key.png +0 -0
  102. data/spec/dummy/public/icons/table_go.png +0 -0
  103. data/spec/dummy/public/icons/time.png +0 -0
  104. data/spec/dummy/public/icons/time_add.png +0 -0
  105. data/spec/dummy/public/icons/time_go.png +0 -0
  106. data/spec/dummy/public/icons/timeline_marker.png +0 -0
  107. data/spec/dummy/public/icons/user_add.png +0 -0
  108. data/spec/dummy/public/icons/user_delete.png +0 -0
  109. data/spec/dummy/public/icons/user_edit.png +0 -0
  110. data/spec/dummy/public/icons/wrench.png +0 -0
  111. data/spec/dummy/script/delayed_job +6 -0
  112. data/spec/dummy/script/rails +6 -0
  113. data/spec/features/javascripts/job_dashboard_live_search.js.coffee +8 -0
  114. data/spec/features/javascripts/login.js.coffee +8 -0
  115. data/spec/features/jobs_dashboard_netzke_spec.rb +24 -0
  116. data/spec/features/jobs_dashboard_spec.rb +49 -0
  117. data/spec/fixtures/scripts/load_tests/script1.dl +2 -0
  118. data/spec/fixtures/scripts/load_tests/script2.dl +2 -0
  119. data/spec/job_helper.rb +102 -0
  120. data/spec/lib/data_exporter_spec.rb +71 -0
  121. data/spec/lib/data_importer_spec.rb +461 -0
  122. data/spec/lib/xl_spec.rb +198 -0
  123. data/spec/lib/xl_styles_spec.rb +115 -0
  124. data/spec/models/api_auth_spec.rb +187 -0
  125. data/spec/models/posting_spec.rb +107 -0
  126. data/spec/models/promise_spec.rb +65 -0
  127. data/spec/models/script_spec.rb +187 -0
  128. data/spec/models/user_spec.rb +68 -0
  129. data/spec/requests/routes_spec.rb +12 -0
  130. data/spec/spec_helper.rb +61 -0
  131. data/spec/support/clean_db_helpers.rb +18 -0
  132. data/spec/support/delayed_job_helpers.rb +12 -0
  133. data/spec/support/user_helpers.rb +12 -0
  134. metadata +139 -89
  135. data/app/components/marty/auth_app.rb~ +0 -51
  136. data/app/components/marty/auth_app/javascripts/auth_app.js~ +0 -91
  137. data/app/components/marty/cm_form_panel.rb~ +0 -5
  138. data/app/components/marty/cm_grid_panel.rb~ +0 -35
  139. data/app/components/marty/data_import_view.rb~ +0 -142
  140. data/app/components/marty/extras/layout.rb~ +0 -46
  141. data/app/components/marty/live_search_grid_panel.rb~ +0 -49
  142. data/app/components/marty/main_auth_app.rb~ +0 -238
  143. data/app/components/marty/mcfly_grid_panel.rb~ +0 -80
  144. data/app/components/marty/new_posting_form.rb~ +0 -46
  145. data/app/components/marty/new_posting_window.rb~ +0 -21
  146. data/app/components/marty/pivot_grid.rb +0 -52
  147. data/app/components/marty/pivot_grid/endpoints.rb +0 -45
  148. data/app/components/marty/pivot_grid/javascripts/extensions.js +0 -150
  149. data/app/components/marty/pivot_grid/javascripts/pivot_grid.js +0 -86
  150. data/app/components/marty/pivot_grid/services.rb +0 -44
  151. data/app/components/marty/posting_grid.rb~ +0 -140
  152. data/app/components/marty/promise_view.rb~ +0 -157
  153. data/app/components/marty/promise_view/stylesheets/promise_view.css~ +0 -15
  154. data/app/components/marty/report_form.rb~ +0 -217
  155. data/app/components/marty/report_select.rb~ +0 -133
  156. data/app/components/marty/reporting.rb~ +0 -39
  157. data/app/components/marty/script_detail.rb~ +0 -430
  158. data/app/components/marty/script_form.rb~ +0 -233
  159. data/app/components/marty/script_form/javascripts/Ext.ux.form.field.CodeMirror.js~ +0 -909
  160. data/app/components/marty/script_grid.rb~ +0 -99
  161. data/app/components/marty/script_tester.rb~ +0 -213
  162. data/app/components/marty/scripting.rb~ +0 -124
  163. data/app/components/marty/select_report.rb~ +0 -143
  164. data/app/components/marty/simple_app.rb~ +0 -101
  165. data/app/components/marty/tag_grid.rb~ +0 -89
  166. data/app/components/marty/tree_panel.rb~ +0 -256
  167. data/app/components/marty/tree_panel/javascripts/tree_panel.js~ +0 -317
  168. data/app/components/marty/user_pivot.rb +0 -128
  169. data/app/components/marty/user_view.rb~ +0 -188
  170. data/app/controllers/marty/application_controller.rb~ +0 -133
  171. data/app/controllers/marty/components_controller.rb~ +0 -37
  172. data/app/controllers/marty/job_controller.rb~ +0 -28
  173. data/app/controllers/marty/rpc_controller.rb~ +0 -61
  174. data/app/helpers/marty/script_set.rb~ +0 -59
  175. data/app/models/marty/api_auth.rb~ +0 -48
  176. data/app/models/marty/data_change.rb~ +0 -141
  177. data/app/models/marty/enum.rb~ +0 -16
  178. data/app/models/marty/import_type.rb~ +0 -48
  179. data/app/models/marty/poop.rb~ +0 -169
  180. data/app/models/marty/posting.rb~ +0 -86
  181. data/app/models/marty/posting_type.rb~ +0 -21
  182. data/app/models/marty/promise.rb~ +0 -196
  183. data/app/models/marty/role.rb~ +0 -10
  184. data/app/models/marty/script.rb~ +0 -62
  185. data/app/models/marty/tag.rb~ +0 -91
  186. data/app/models/marty/user.rb~ +0 -148
  187. data/app/models/marty/user_role.rb~ +0 -13
  188. data/app/views/layouts/marty/application.html.erb~ +0 -11
  189. data/config/routes.rb~ +0 -10
  190. data/db/migrate/019_create_marty_postings.rb~ +0 -19
  191. data/db/migrate/095_create_marty_tags.rb~ +0 -19
  192. data/lib/marty.rb~ +0 -13
  193. data/lib/marty/content_handler.rb~ +0 -93
  194. data/lib/marty/data_exporter.rb~ +0 -137
  195. data/lib/marty/data_importer.rb~ +0 -114
  196. data/lib/marty/data_row_processor.rb~ +0 -206
  197. data/lib/marty/drop_folder_hook.rb~ +0 -17
  198. data/lib/marty/folder_hook.rb~ +0 -9
  199. data/lib/marty/lazy_column_loader.rb~ +0 -47
  200. data/lib/marty/mcfly_query.rb~ +0 -188
  201. data/lib/marty/migrations.rb~ +0 -65
  202. data/lib/marty/monkey.rb~ +0 -160
  203. data/lib/marty/permissions.rb~ +0 -69
  204. data/lib/marty/promise.rb~ +0 -41
  205. data/lib/marty/promise_job.rb~ +0 -121
  206. data/lib/marty/promise_proxy.rb~ +0 -69
  207. data/lib/marty/util.rb~ +0 -80
  208. data/lib/marty/version.rb~ +0 -3
  209. data/lib/marty/xl.rb~ +0 -526
  210. data/lib/pyxll/README.txt~ +0 -16
  211. data/lib/pyxll/gemini.py~ +0 -110
  212. data/lib/pyxll/pyxll.cfg~ +0 -12
@@ -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
@@ -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
-
@@ -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
@@ -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
@@ -1,3 +0,0 @@
1
- module Marty
2
- VERSION = "0.3.5"
3
- end
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