mobilize-base 1.0.94 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -501,7 +501,7 @@ name>))` and enter values under each header:
501
501
  * status Mobilize writes this field with the last status returned by the job
502
502
 
503
503
  * stage1..stage5 List of stages to be performed by the job.
504
- * Stages have this syntax: <handler>.<call> <params>.
504
+ * Stages have this syntax: `<handler>.<call> <params>`.
505
505
  * handler specifies the file that should receive the stage
506
506
  * the call specifies the method within the file. The method should
507
507
  be called `"<handler>.<call>_by_stage_path"`
@@ -514,14 +514,14 @@ curly braces).
514
514
  * gsheet.read `source: <input_gsheet_full_path>`, which reads the sheet.
515
515
  * The gsheet_full_path should be of the form `<gbook_name>/<gsheet_name>`. The test uses
516
516
  "Requestor_mobilize(test)/base1_stage1.in".
517
- * gsheet.write `source: <stage_relative_path>`,`target: <target_gsheet_path>`,
517
+ * gsheet.write `source: <stage_name>`,`target: <target_gsheet_path>`,
518
518
  which writes the specified stage output to the target_gsheet.
519
- * The stage_relative_path should be of the form `<stage_column>` or
520
- `<job_name/stage_column>`. The test uses "base1/stage1" for the first test
521
- and simply "stage1" for the second test. Both of these take the output
522
- from the first stage.
519
+ * The stage_name should be of the form `<stage_column>`. The test uses "stage1" for the first test
520
+ and "Runner_mobilize(test)/base1.out" for the second test. The first
521
+ takes the output from the first stage and the second reads it straight
522
+ from the referenced sheet.
523
523
  * The test uses "Requestor_mobilize(test)/base1.out" and
524
- "Requestor_mobilize(test)/base2.out" for target sheets.
524
+ "Runner_mobilize(test)/base2.out" for target sheets.
525
525
 
526
526
  <a name='section_Start_Run_Test'></a>
527
527
  ### Run Test
@@ -40,6 +40,16 @@ module GoogleDrive
40
40
  end
41
41
  end
42
42
 
43
+ def read(user)
44
+ f = self
45
+ entry = f.acl_entry("#{user}@#{Mobilize::Gdrive.domain}")
46
+ if entry and ['reader','writer','owner'].include?(entry.role)
47
+ f.download_to_string
48
+ else
49
+ raise "User #{user} is not allowed to read #{f.title}"
50
+ end
51
+ end
52
+
43
53
  def update_acl(email,role="writer")
44
54
  f = self
45
55
  #need these flags for HTTP retries
@@ -70,15 +80,11 @@ module GoogleDrive
70
80
  f = self
71
81
  f.acls.select{|a| ['group','user'].include?(a.scope_type) and a.scope == email}.first
72
82
  end
73
-
74
83
  def entry_hash
75
84
  f = self
76
85
  dfe_xml = f.document_feed_entry.to_xml
77
- begin
78
- Hash.from_xml(dfe_xml)[:entry]
79
- rescue
80
- {}
81
- end
86
+ result = Nokogiri::XML(dfe_xml)
87
+ { result.root.name.to_sym => Hash.xml_node_to_hash(result.root)}[:entry]
82
88
  end
83
89
  end
84
90
  end
@@ -44,11 +44,15 @@ module GoogleDrive
44
44
  sheet.save
45
45
  end
46
46
 
47
- def merge(merge_sheet)
47
+ def merge(merge_sheet,user)
48
48
  #write the top left of sheet
49
49
  #with the contents of merge_sheet
50
50
  sheet = self
51
51
  sheet.reload
52
+ entry = merge_sheet.spreadsheet.acl_entry("#{user}@#{Mobilize::Gdrive.domain}")
53
+ unless entry and ['writer','owner'].include?(entry.role)
54
+ raise "User #{user} is not allowed to write to #{merge_sheet.spreadsheet.title}"
55
+ end
52
56
  merge_sheet.reload
53
57
  curr_rows = sheet.num_rows
54
58
  curr_cols = sheet.num_cols
@@ -77,8 +81,22 @@ module GoogleDrive
77
81
  sheet.save
78
82
  end
79
83
 
80
- def write(tsv)
84
+ def read(user)
81
85
  sheet = self
86
+ entry = sheet.spreadsheet.acl_entry("#{user}@#{Mobilize::Gdrive.domain}")
87
+ if entry and ['reader','writer','owner'].include?(entry.role)
88
+ sheet.to_tsv
89
+ else
90
+ raise "User #{user} is not allowed to read #{sheet.spreadsheet.title}"
91
+ end
92
+ end
93
+
94
+ def write(tsv,user)
95
+ sheet = self
96
+ entry = sheet.spreadsheet.acl_entry("#{user}@#{Mobilize::Gdrive.domain}")
97
+ unless entry and ['writer','owner'].include?(entry.role)
98
+ raise "User #{user} is not allowed to write to #{sheet.spreadsheet.title}"
99
+ end
82
100
  tsvrows = tsv.split("\n")
83
101
  #no rows, no write
84
102
  return true if tsvrows.length==0
@@ -29,55 +29,49 @@ class Hash
29
29
  return self
30
30
  end
31
31
  # BEGIN methods to create hash from XML
32
- class << self
33
- def from_xml(xml_io)
34
- begin
35
- result = Nokogiri::XML(xml_io)
36
- return { result.root.name.to_sym => xml_node_to_hash(result.root)}
37
- rescue Exception => e
38
- # raise your custom exception here
39
- end
40
- end
41
- def xml_node_to_hash(node)
42
- # If we are at the root of the document, start the hash
43
- if node.element?
44
- result_hash = {}
45
- if node.attributes != {}
46
- result_hash[:attributes] = {}
47
- node.attributes.keys.each do |key|
48
- result_hash[:attributes][node.attributes[key].name.to_sym] = prepare(node.attributes[key].value)
49
- end
32
+ def Hash.from_xml(xml_io)
33
+ result = Nokogiri::XML(xml_io)
34
+ return { result.root.name.to_sym => Hash.xml_node_to_hash(result.root)}
35
+ end
36
+ def Hash.xml_node_to_hash(node)
37
+ # If we are at the root of the document, start the hash
38
+ if node.element?
39
+ result_hash = {}
40
+ if node.attributes != {}
41
+ result_hash[:attributes] = {}
42
+ node.attributes.keys.each do |key|
43
+ result_hash[:attributes][node.attributes[key].name.to_sym] = prepare(node.attributes[key].value)
50
44
  end
51
- if node.children.size > 0
52
- node.children.each do |child|
53
- result = xml_node_to_hash(child)
45
+ end
46
+ if node.children.size > 0
47
+ node.children.each do |child|
48
+ result = xml_node_to_hash(child)
54
49
 
55
- if child.name == "text"
56
- unless child.next_sibling || child.previous_sibling
57
- return prepare(result)
58
- end
59
- elsif result_hash[child.name.to_sym]
60
- if result_hash[child.name.to_sym].is_a?(Object::Array)
61
- result_hash[child.name.to_sym] << prepare(result)
62
- else
63
- result_hash[child.name.to_sym] = [result_hash[child.name.to_sym]] << prepare(result)
64
- end
65
- else
66
- result_hash[child.name.to_sym] = prepare(result)
50
+ if child.name == "text"
51
+ unless child.next_sibling || child.previous_sibling
52
+ return prepare(result)
67
53
  end
54
+ elsif result_hash[child.name.to_sym]
55
+ if result_hash[child.name.to_sym].is_a?(Object::Array)
56
+ result_hash[child.name.to_sym] << prepare(result)
57
+ else
58
+ result_hash[child.name.to_sym] = [result_hash[child.name.to_sym]] << prepare(result)
59
+ end
60
+ else
61
+ result_hash[child.name.to_sym] = prepare(result)
68
62
  end
63
+ end
69
64
 
70
- return result_hash
71
- else
72
- return result_hash
73
- end
65
+ return result_hash
74
66
  else
75
- return prepare(node.content.to_s)
67
+ return result_hash
76
68
  end
77
- end
78
- def prepare(data)
79
- (data.class == String && data.to_i.to_s == data) ? data.to_i : data
80
- end
69
+ else
70
+ return prepare(node.content.to_s)
71
+ end
72
+ end
73
+ def Hash.prepare(data)
74
+ (data.class == String && data.to_i.to_s == data) ? data.to_i : data
81
75
  end
82
76
  def to_struct(struct_name)
83
77
  Struct.new(struct_name,*keys).new(*values)
@@ -12,6 +12,10 @@ module Mobilize
12
12
  [Gdrive.config['owner']['name'],Gdrive.domain].join("@")
13
13
  end
14
14
 
15
+ def Gdrive.owner_name
16
+ Gdrive.config['owner']['name']
17
+ end
18
+
15
19
  def Gdrive.password(email)
16
20
  if email == Gdrive.owner_email
17
21
  Gdrive.config['owner']['pw']
@@ -18,10 +18,6 @@ module Mobilize
18
18
  file.update_acl(gdrive_slot,role)
19
19
  end
20
20
 
21
- def Gfile.find_by_path(path,gdrive_slot)
22
- Gdrive.files(gdrive_slot,{"title"=>path,"title-exact"=>"true"}).first
23
- end
24
-
25
21
  def Gfile.read_by_stage_path(stage_path)
26
22
  #reserve gdrive_slot account for read
27
23
  gdrive_slot = Gdrive.slot_worker_by_path(s.path)
@@ -31,7 +27,37 @@ module Mobilize
31
27
  out_tsv = Gfile.find_by_path(gfile_path,gdrive_slot).read
32
28
  #use Gridfs to cache result
33
29
  out_url = "gridfs://#{s.path}/out"
34
- Dataset.write_to_url(out_url,out_tsv)
30
+ Dataset.write_by_url(out_url,out_tsv,s.job.runner.user.name)
31
+ end
32
+
33
+ def Gfile.find_by_path(path)
34
+ #file must be owned by owner
35
+ gdrive_slot = Gdrive.owner_email
36
+ files = Gdrive.files(gdrive_slot,{"title"=>path,"title-exact"=>"true"})
37
+ dst = Dataset.find_or_create_by_handler_and_path('gfile',path)
38
+ #there should only be one file with each path, otherwise we have fail
39
+ file = nil
40
+ if files.length>1
41
+ #keep most recent file, delete the rest
42
+ files.sort_by do |f|
43
+ (f.entry_hash[:published] || Time.now).to_time
44
+ end.reverse.each_with_index do |f,f_i|
45
+ if f_i == 0
46
+ file = f
47
+ else
48
+ #delete the old file
49
+ f.delete
50
+ ("Deleted duplicate file #{path}").oputs
51
+ end
52
+ end
53
+ else
54
+ file = files.first
55
+ end
56
+ #always make sure dataset http URL is up to date
57
+ #and that it has admin acl
58
+ dst.update_attributes(:http_url=>file.human_url)
59
+ file.add_admin_acl
60
+ return file
35
61
  end
36
62
  end
37
63
  end
@@ -11,7 +11,7 @@ module Mobilize
11
11
  return ::Mongo::GridFileSystem.new(::Mongo::Connection.new(host,port).db(database_name))
12
12
  end
13
13
 
14
- def Gridfs.read_by_dataset_path(dst_path)
14
+ def Gridfs.read_by_dataset_path(dst_path,user)
15
15
  begin
16
16
  zs=Gridfs.grid.open(dst_path,'r').read
17
17
  return ::Zlib::Inflate.inflate(zs)
@@ -20,10 +20,10 @@ module Mobilize
20
20
  end
21
21
  end
22
22
 
23
- def Gridfs.write_by_dataset_path(dst_path,string)
23
+ def Gridfs.write_by_dataset_path(dst_path,string,user)
24
24
  zs = ::Zlib::Deflate.deflate(string)
25
25
  raise "compressed string too large for Gridfs write" if zs.length > Gridfs.config['max_compressed_write_size']
26
- curr_zs = Gridfs.read_by_dataset_path(dst_path).to_s
26
+ curr_zs = Gridfs.read_by_dataset_path(dst_path,user).to_s
27
27
  #write a new version when there is a change
28
28
  if curr_zs != zs
29
29
  Gridfs.grid.open(dst_path,'w',:versions => Gridfs.config['max_versions']){|f| f.write(zs)}
@@ -37,11 +37,12 @@ module Mobilize
37
37
  gdrive_slot = Gdrive.slot_worker_by_path(stage_path)
38
38
  return false unless gdrive_slot
39
39
  s = Stage.where(:path=>stage_path).first
40
+ user = s.job.runner.user.name
40
41
  gsheet_path = s.params['source']
41
- out_tsv = Gsheet.find_by_path(gsheet_path,gdrive_slot).to_tsv
42
+ out_tsv = Gsheet.find_by_path(gsheet_path,gdrive_slot).read(user)
42
43
  #use Gridfs to cache result
43
44
  out_url = "gridfs://#{s.path}/out"
44
- Dataset.write_to_url(out_url,out_tsv)
45
+ Dataset.write_by_url(out_url,out_tsv,Gdrive.owner_name)
45
46
  end
46
47
 
47
48
  def Gsheet.write_by_stage_path(stage_path)
@@ -49,24 +50,24 @@ module Mobilize
49
50
  #return blank response if there are no slots available
50
51
  return nil unless gdrive_slot
51
52
  s = Stage.where(:path=>stage_path).first
52
- source_path = s.params['source']
53
+ user = s.job.runner.user.name
53
54
  target_path = s.params['target']
54
- source_dst = s.source_dst(source_path)
55
- tsv = source_dst.read
55
+ source_dst = s.source_dsts(gdrive_slot).first
56
+ tsv = source_dst.read(user)
56
57
  sheet_name = target_path.split("/").last
57
58
  temp_path = [stage_path.gridsafe,sheet_name].join("/")
58
59
  temp_sheet = Gsheet.find_or_create_by_path(temp_path,gdrive_slot)
59
- temp_sheet.write(tsv)
60
+ temp_sheet.write(tsv,Gdrive.owner_name)
60
61
  temp_sheet.check_and_fix(tsv)
61
62
  target_sheet = Gsheet.find_or_create_by_path(target_path,gdrive_slot)
62
- target_sheet.merge(temp_sheet)
63
+ target_sheet.merge(temp_sheet,user)
63
64
  #delete the temp sheet's book
64
65
  temp_sheet.spreadsheet.delete
65
66
  status = "Write successful for #{target_path}"
66
67
  s.update_status(status)
67
68
  #use Gridfs to cache result
68
69
  out_url = "gridfs://#{s.path}/out"
69
- Dataset.write_to_url(out_url,status)
70
+ Dataset.write_by_url(out_url,status,Gdrive.owner_name)
70
71
  end
71
72
  end
72
73
  end
@@ -38,30 +38,24 @@ module Mobilize
38
38
  return dst
39
39
  end
40
40
 
41
- def Dataset.write_to_url(url,string)
41
+ def Dataset.write_by_url(url,string,user)
42
42
  dst = Dataset.find_or_create_by_url(url)
43
- dst.write(string)
43
+ dst.write(string,user)
44
44
  url
45
45
  end
46
46
 
47
- def read
47
+ def read(user)
48
48
  dst = self
49
49
  dst.update_attributes(:last_read_at=>Time.now.utc)
50
- "Mobilize::#{dst.handler.humanize}".constantize.read_by_dataset_path(dst.path)
50
+ "Mobilize::#{dst.handler.humanize}".constantize.read_by_dataset_path(dst.path,user)
51
51
  end
52
52
 
53
- def write(string)
53
+ def write(string,user)
54
54
  dst = self
55
- "Mobilize::#{dst.handler.humanize}".constantize.write_by_dataset_path(dst.path,string)
55
+ "Mobilize::#{dst.handler.humanize}".constantize.write_by_dataset_path(dst.path,string,user)
56
56
  dst.raw_size = string.length
57
57
  dst.save!
58
58
  return true
59
59
  end
60
-
61
- def delete
62
- dst = self
63
- "Mobilize::#{dst.handler.humanize}".constantize.delete_by_dataset_path(dst.path)
64
- return true
65
- end
66
60
  end
67
61
  end
@@ -72,6 +72,12 @@ module Mobilize
72
72
  Dataset.find_or_create_by_url("gridfs://#{r.path}")
73
73
  end
74
74
 
75
+ def gbook(gdrive_slot)
76
+ r = self
77
+ title = r.path.split("/").first
78
+ Gbook.find_all_by_path(title,gdrive_slot).first
79
+ end
80
+
75
81
  def gsheet(gdrive_slot)
76
82
  r = self
77
83
  jobs_sheet = Gsheet.find_or_create_by_path(r.path,gdrive_slot)
@@ -82,9 +88,9 @@ module Mobilize
82
88
 
83
89
  def read_gsheet(gdrive_slot)
84
90
  r = self
85
- gsheet_tsv = r.gsheet(gdrive_slot).to_tsv
91
+ gsheet_tsv = r.gsheet(gdrive_slot).read(Gdrive.owner_name)
86
92
  #cache in DB
87
- r.cache.write(gsheet_tsv)
93
+ r.cache.write(gsheet_tsv,Gdrive.owner_name)
88
94
  #turn it into a hash array
89
95
  gsheet_jobs = gsheet_tsv.tsv_to_hash_array
90
96
  #go through each job, update relevant job with its params
@@ -68,19 +68,6 @@ module Mobilize
68
68
  return j.stages[s.idx]
69
69
  end
70
70
 
71
- def source_dst(source_path)
72
- #gets dataset based on path given in source parameter
73
- s = self
74
- source_job_name, source_stage_name = if source_path.index("/")
75
- source_path.split("/")
76
- else
77
- [nil, source_path]
78
- end
79
- source_stage_path = "#{s.job.runner.path}/#{source_job_name || s.job.name}/#{source_stage_name}"
80
- source_stage = Stage.where(:path=>source_stage_path).first
81
- source_stage.out_dst
82
- end
83
-
84
71
  def Stage.perform(id,*args)
85
72
  s = Stage.where(:path=>id).first
86
73
  j = s.job
@@ -118,6 +105,53 @@ module Mobilize
118
105
  return true
119
106
  end
120
107
 
108
+ def source_dsts(gdrive_slot)
109
+ #returns an array of Datasets corresponding to
110
+ #gridfs caches for stage outputs, gsheets and gfiles
111
+ #or dataset pointers for other handlers
112
+ s = self
113
+ params = s.params
114
+ source_paths = if params['sources']
115
+ params['sources']
116
+ elsif params['source']
117
+ [params['source']]
118
+ end
119
+ user = s.job.runner.user.name
120
+ return [] if (source_paths.class!=Array or source_paths.length==0)
121
+ dsts = []
122
+ source_paths.each do |source_path|
123
+ if source_path.index(/^stage[1-5]$/)
124
+ source_stage_path = "#{s.job.runner.path}/#{s.job.name}/#{source_path}"
125
+ source_stage = Stage.where(:path=>source_stage_path).first
126
+ dsts << source_stage.out_dst
127
+ elsif source_path.index("://")
128
+ #find or create by url
129
+ dsts << Dataset.find_or_create_by_url(source_path)
130
+ else
131
+ if source_path.index("/")
132
+ #slashes mean sheets
133
+ out_tsv = Gsheet.find_by_path(source_path,gdrive_slot).read(user)
134
+ else
135
+ #check sheets in runner
136
+ r = s.job.runner
137
+ runner_sheet = r.gbook.worksheet_by_title(source_path)
138
+ out_tsv = if runner_sheet
139
+ runner_sheet.read(user)
140
+ else
141
+ #check for gfile. will fail if there isn't one.
142
+ Gfile.find_by_path(source_path).read(user)
143
+ end
144
+ end
145
+ #use Gridfs to cache gdrive results
146
+ file_name = source_path.split("/").last
147
+ out_url = "gridfs://#{s.path}/#{file_name}"
148
+ Dataset.write_by_url(out_url,out_tsv,user)
149
+ dsts << Dataset.find_by_url(out_url)
150
+ end
151
+ end
152
+ return dsts
153
+ end
154
+
121
155
  def enqueue!
122
156
  s = self
123
157
  ::Resque::Job.create("mobilize",Stage,s.path,{})
@@ -65,7 +65,10 @@ namespace :mobilize_base do
65
65
  resque_web_extension_path = "#{full_config_dir}resque_web.rb"
66
66
  #kill any resque-web for now
67
67
  `ps aux | grep resque-web | awk '{print $2}' | xargs kill`
68
- command = "bundle exec resque-web -p #{port.to_s} #{resque_web_extension_path}"
68
+ resque_redis_port_args = if Mobilize::Base.env == 'test'
69
+ " -r localhost:#{Mobilize::Base.config('resque')['redis_port']}"
70
+ end.to_s
71
+ command = "bundle exec resque-web -p #{port.to_s} #{resque_web_extension_path} #{resque_redis_port_args}"
69
72
  `#{command}`
70
73
  end
71
74
  desc "create indexes for all base models in mongodb"
@@ -1,5 +1,5 @@
1
1
  module Mobilize
2
2
  module Base
3
- VERSION = "1.0.94"
3
+ VERSION = "1.1.0"
4
4
  end
5
5
  end
@@ -1,13 +1,12 @@
1
- - name: "base1"
1
+ - name: base1
2
2
  active: true
3
3
  trigger: once
4
4
  status: ""
5
- stage1: 'gsheet.read source:"Runner_mobilize(test)/base1_stage1.in"'
6
- stage2: 'gsheet.write source:"base1/stage1", target:"Runner_mobilize(test)/base1.out"'
5
+ stage1: gsheet.read source:"Runner_mobilize(test)/base1_stage1.in"
6
+ stage2: gsheet.write source:"stage1", target:"Runner_mobilize(test)/base1.out"
7
7
 
8
- - name: "base2"
8
+ - name: base2
9
9
  active: true
10
- trigger: "after base1"
10
+ trigger: after base1
11
11
  status: ""
12
- stage1: 'gsheet.read source:"Runner_mobilize(test)/base1.out"'
13
- stage2: 'gsheet.write source:"stage1", target:"Runner_mobilize(test)/base2.out"'
12
+ stage1: gsheet.write source:"Runner_mobilize(test)/base1.out", target:"Runner_mobilize(test)/base2.out"
@@ -31,7 +31,7 @@ describe "Mobilize" do
31
31
  puts "Jobtracker created runner with 'jobs' sheet?"
32
32
  r = u.runner
33
33
  jobs_sheet = r.gsheet(gdrive_slot)
34
- tsv = jobs_sheet.to_tsv
34
+ tsv = jobs_sheet.read(Mobilize::Gdrive.owner_name)
35
35
  assert tsv.length == 61 #headers only
36
36
 
37
37
  puts "add base1_stage1 input sheet"
@@ -39,7 +39,7 @@ describe "Mobilize" do
39
39
 
40
40
  test_source_ha = ::YAML.load_file("#{Mobilize::Base.root}/test/base1_stage1.yml")*40
41
41
  test_source_tsv = test_source_ha.hash_array_to_tsv
42
- test_source_sheet.write(test_source_tsv)
42
+ test_source_sheet.write(test_source_tsv,Mobilize::Gdrive.owner_name)
43
43
 
44
44
  puts "add row to jobs sheet, wait 150s"
45
45
  test_job_rows = ::YAML.load_file("#{Mobilize::Base.root}/test/base_job_rows.yml")
@@ -57,7 +57,7 @@ describe "Mobilize" do
57
57
 
58
58
  jobs_sheet.add_or_update_rows([{'name'=>'base1','active'=>true}])
59
59
  sleep 120
60
-
60
+
61
61
  test_target_sheet_2 = Mobilize::Gsheet.find_by_path("#{r.path.split("/")[0..-2].join("/")}/base1.out",gdrive_slot)
62
62
  puts "jobtracker posted test sheet data to test destination, and checksum succeeded?"
63
63
  assert test_target_sheet_2.to_tsv == test_source_sheet.to_tsv
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mobilize-base
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.94
4
+ version: 1.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-08 00:00:00.000000000 Z
12
+ date: 2013-01-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -238,7 +238,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
238
238
  version: '0'
239
239
  segments:
240
240
  - 0
241
- hash: -2093825275385186120
241
+ hash: -781501523970053172
242
242
  required_rubygems_version: !ruby/object:Gem::Requirement
243
243
  none: false
244
244
  requirements:
@@ -247,7 +247,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
247
247
  version: '0'
248
248
  segments:
249
249
  - 0
250
- hash: -2093825275385186120
250
+ hash: -781501523970053172
251
251
  requirements: []
252
252
  rubyforge_project: mobilize-base
253
253
  rubygems_version: 1.8.24