PageRenderTime 61ms CodeModel.GetById 33ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/tender_import/archive.rb

https://github.com/yoyolvturner/tender_import_scripts
Ruby | 216 lines | 146 code | 22 blank | 48 comment | 29 complexity | 6a6ebc4f0cd5167fb81b5956776fdb21 MD5 | raw file
  1. #
  2. # Provides a Ruby API for constructing Tender import archives.
  3. #
  4. # https://help.tenderapp.com/faqs/setup-installation/importing
  5. #
  6. # ## Example
  7. #
  8. # # Currently requires a site name.
  9. # archive = TenderImport::Archive.new('tacotown')
  10. #
  11. # # Can add users, categories and discussions to the archive.
  12. # archive.add_user :email => 'frank@tacotown.com', :state => 'support'
  13. # archive.add_user :email => 'bob@bobfoo.com'
  14. #
  15. # # When you add a category you'll get a handle needed to add discusions.
  16. # category = archive.add_category :name => 'Tacos'
  17. #
  18. # # Discussions must have at least one comment.
  19. # archive.add_discussion category, :title => 'your tacos',
  20. # :author_email => 'bob@bobfoo.com',
  21. # :comments => [{
  22. # :author_email => 'bob@bobfoo.com',
  23. # :body => 'They are not so good.'
  24. # }, {
  25. # :author_email => 'frank@tacotown.com',
  26. # :body => 'You have terrible taste in tacos. Good day, sir.'
  27. # }]
  28. #
  29. # # By default, files are written as you add them, so this will just
  30. # # assemble a gzipped tar archive from those files.
  31. # filename = archive.write_archive
  32. # puts "your import file is #{filename}"
  33. #
  34. # # If any errors are reported, some records were not included in the archive.
  35. # if !archive.report.empty?
  36. # puts "Problems reported: ", *archive.report
  37. # end
  38. #
  39. require 'yajl'
  40. require 'fileutils'
  41. class TenderImport::Archive
  42. class Error < StandardError; end
  43. include FileUtils
  44. attr_reader :site, :report, :stats, :buffer
  45. # Options:
  46. #
  47. # buffer:: When true, don't flush to disk until the end. Defaults to false.
  48. #
  49. def initialize site_name, options = {}
  50. @site = site_name
  51. @export_dir = ".#{site_name}-export-#{$$}"
  52. @report = []
  53. @import = {}
  54. @stats = {}
  55. @buffer = options.key?(:buffer) ? !!options[:buffer] : false
  56. @category_counter = Hash.new(0)
  57. end
  58. # Returns the params on success, nil on failure
  59. def add_user params
  60. validate_and_store :user, {:state => 'user'}.merge(params)
  61. end
  62. # Returns a handle needed for adding discussions
  63. def add_category params
  64. cat = validate_and_store :category, params
  65. cat ? category_key(cat) : nil
  66. end
  67. def add_discussion category_key, params
  68. raise Error, "add_discussion: missing category key" if category_key.nil?
  69. validate_and_store :discussion, params, :key => category_key
  70. end
  71. def category_key cat
  72. "category:#{category_id cat}".downcase
  73. end
  74. def category_id cat
  75. cat[:name].gsub(/\W+/,'_').downcase
  76. end
  77. def categories
  78. @import[:category]
  79. end
  80. def discussions category_key
  81. raise Error, "discussions: missing category key" if category_key.nil?
  82. @import[category_key] || []
  83. end
  84. def users
  85. @import[:user]
  86. end
  87. def write_archive
  88. write_users if users
  89. write_categories_and_discussions if categories
  90. export_file = "export_#{site}.tgz"
  91. system "tar -zcf #{export_file} -C #{export_dir} ."
  92. system "rm -rf #{export_dir}"
  93. return export_file
  94. end
  95. def write_user user
  96. return unless user
  97. mkdir_p export_dir('users')
  98. File.open(File.join(export_dir('users'), "#{user[:email].gsub(/\W+/,'_')}.json"), "w") do |file|
  99. file.puts Yajl::Encoder.encode(user)
  100. end
  101. end
  102. def write_users
  103. users.each do |u|
  104. write_user u
  105. end
  106. end
  107. def write_category c
  108. mkdir_p export_dir('categories')
  109. File.open(File.join(export_dir('categories'), "#{category_id(c)}.json"), "w") do |file|
  110. file.puts Yajl::Encoder.encode(c)
  111. end
  112. end
  113. def write_categories_and_discussions
  114. categories.each do |c|
  115. write_category c
  116. write_discussions c
  117. end
  118. end
  119. def write_discussion category_id, discussion
  120. @category_counter[category_id] += 1
  121. dir = File.join(export_dir('categories'), category_id)
  122. mkdir_p dir
  123. File.open(File.join(dir, "#{@category_counter[category_id]}.json"), "w") do |file|
  124. file.puts Yajl::Encoder.encode(discussion)
  125. end
  126. end
  127. def write_discussions category
  128. discussions(category_key(category)).each do |d|
  129. write_discussion category_id(category), d
  130. end
  131. end
  132. protected
  133. def validate_and_store *args
  134. type, params, options = args
  135. options ||= {}
  136. key = options[:key] || type
  137. @import[key] ||= []
  138. if valid? type, params
  139. if buffer
  140. # save in memory and flush to disk at the end
  141. @import[key] << params
  142. else
  143. # write immediately instead of storing in memory
  144. write *args
  145. end
  146. @stats[key] ||= 0
  147. @stats[key] += 1
  148. params
  149. else
  150. @stats["invalid:#{key}"] ||= 0
  151. @stats["invalid:#{key}"] += 1
  152. nil
  153. end
  154. end
  155. def write type, params, options = {}
  156. case type
  157. when :discussion
  158. # ughh
  159. write_discussion options[:key].split(':',2)[1], params
  160. when :category
  161. write_category params
  162. when :user
  163. write_user params
  164. end
  165. end
  166. def valid? type, params
  167. problems = []
  168. # XXX this is not really enough validation, also it's ugly as fuck
  169. if type == :user && (params[:email].nil? || params[:email].empty?)
  170. problems << "Missing email in user data: #{params.inspect}."
  171. end
  172. if type == :user && !%w[user support].include?(params[:state])
  173. problems << "Invalid state in user data: #{params.inspect}."
  174. end
  175. if type == :category && (params[:name].nil? || params[:name].empty?)
  176. problems << "Missing name in category data: #{params.inspect}."
  177. end
  178. if type == :discussion && (params[:author_email].nil? || params[:author_email].empty?)
  179. problems << "Missing author_email in discussion data: #{params.inspect}."
  180. end
  181. if type == :discussion && (params[:comments].nil? || params[:comments].any? {|c| c[:author_email].nil? || c[:author_email].empty?})
  182. problems << "Missing comments and authors in discussion data: #{params.inspect}."
  183. end
  184. if problems.empty?
  185. true
  186. else
  187. @report += problems
  188. false
  189. end
  190. end
  191. def export_dir subdir=nil
  192. subdir.nil? ? @export_dir : File.join(@export_dir, subdir.to_s)
  193. end
  194. end