paperclip 3.5.2 → 3.5.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of paperclip might be problematic. Click here for more details.

Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +9 -0
  3. data/Appraisals +3 -3
  4. data/Gemfile +3 -8
  5. data/NEWS +22 -0
  6. data/README.md +61 -12
  7. data/gemfiles/3.0.gemfile +2 -2
  8. data/gemfiles/3.1.gemfile +2 -2
  9. data/gemfiles/3.2.gemfile +2 -2
  10. data/gemfiles/4.0.gemfile +2 -2
  11. data/lib/paperclip.rb +3 -2
  12. data/lib/paperclip/attachment.rb +13 -2
  13. data/lib/paperclip/attachment_registry.rb +3 -1
  14. data/lib/paperclip/content_type_detector.rb +2 -4
  15. data/lib/paperclip/geometry_detector_factory.rb +8 -3
  16. data/lib/paperclip/has_attached_file.rb +1 -1
  17. data/lib/paperclip/interpolations.rb +4 -4
  18. data/lib/paperclip/io_adapters/abstract_adapter.rb +1 -1
  19. data/lib/paperclip/io_adapters/data_uri_adapter.rb +1 -1
  20. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +1 -1
  21. data/lib/paperclip/storage/fog.rb +16 -8
  22. data/lib/paperclip/storage/s3.rb +26 -14
  23. data/lib/paperclip/tempfile_factory.rb +1 -3
  24. data/lib/paperclip/validators.rb +3 -1
  25. data/lib/paperclip/version.rb +1 -1
  26. data/paperclip.gemspec +0 -4
  27. data/shoulda_macros/paperclip.rb +14 -3
  28. data/test/attachment_registry_test.rb +11 -0
  29. data/test/attachment_test.rb +30 -1
  30. data/test/content_type_detector_test.rb +1 -0
  31. data/test/file_command_content_type_detector_test.rb +2 -0
  32. data/test/generator_test.rb +6 -2
  33. data/test/has_attached_file_test.rb +3 -3
  34. data/test/helper.rb +18 -1
  35. data/test/integration_test.rb +4 -5
  36. data/test/io_adapters/abstract_adapter_test.rb +1 -1
  37. data/test/io_adapters/attachment_adapter_test.rb +6 -3
  38. data/test/io_adapters/data_uri_adapter_test.rb +7 -0
  39. data/test/io_adapters/empty_string_adapter_test.rb +1 -0
  40. data/test/io_adapters/file_adapter_test.rb +48 -32
  41. data/test/storage/fog_test.rb +28 -8
  42. data/test/storage/s3_test.rb +8 -0
  43. data/test/tempfile_factory_test.rb +4 -0
  44. data/test/validators/attachment_content_type_validator_test.rb +1 -0
  45. data/test/validators_test.rb +29 -0
  46. metadata +42 -95
@@ -1,7 +1,7 @@
1
1
  module Paperclip
2
2
  class DataUriAdapter < StringioAdapter
3
3
 
4
- REGEXP = /^data:([-\w]+\/[-\w\+]+);base64,(.*)/m
4
+ REGEXP = /\Adata:([-\w]+\/[-\w\+]+);base64,(.*)/m
5
5
 
6
6
  def initialize(target_uri)
7
7
  @target_uri = target_uri
@@ -1,7 +1,7 @@
1
1
  module Paperclip
2
2
  class HttpUrlProxyAdapter < UriAdapter
3
3
 
4
- REGEXP = /^https?:\/\//
4
+ REGEXP = /\Ahttps?:\/\//
5
5
 
6
6
  def initialize(target)
7
7
  super(URI(target))
@@ -43,8 +43,8 @@ module Paperclip
43
43
  end unless defined?(Fog)
44
44
 
45
45
  base.instance_eval do
46
- unless @options[:url].to_s.match(/^:fog.*url$/)
47
- @options[:path] = @options[:path].gsub(/:url/, @options[:url]).gsub(/^:rails_root\/public\/system\//, '')
46
+ unless @options[:url].to_s.match(/\A:fog.*url\Z/)
47
+ @options[:path] = @options[:path].gsub(/:url/, @options[:url]).gsub(/\A:rails_root\/public\/system\//, '')
48
48
  @options[:url] = ':fog_public_url'
49
49
  end
50
50
  Paperclip.interpolates(:fog_public_url) do |attachment, style|
@@ -53,7 +53,7 @@ module Paperclip
53
53
  end
54
54
  end
55
55
 
56
- AWS_BUCKET_SUBDOMAIN_RESTRICTON_REGEX = /^(?:[a-z]|\d(?!\d{0,2}(?:\.\d{1,3}){3}$))(?:[a-z0-9]|\.(?![\.\-])|\-(?![\.])){1,61}[a-z0-9]$/
56
+ AWS_BUCKET_SUBDOMAIN_RESTRICTON_REGEX = /\A(?:[a-z]|\d(?!\d{0,2}(?:\.\d{1,3}){3}\Z))(?:[a-z0-9]|\.(?![\.\-])|\-(?![\.])){1,61}[a-z0-9]\Z/
57
57
 
58
58
  def exists?(style = default_style)
59
59
  if original_filename
@@ -138,15 +138,16 @@ module Paperclip
138
138
  end
139
139
  end
140
140
 
141
- def expiring_url(time = (Time.now + 3600), style = default_style)
142
- if directory.files.respond_to?(:get_http_url)
143
- expiring_url = directory.files.get_http_url(path(style), time)
141
+ def expiring_url(time = (Time.now + 3600), style_name = default_style)
142
+ time = convert_time(time)
143
+ if path(style_name) && directory.files.respond_to?(:get_http_url)
144
+ expiring_url = directory.files.get_http_url(path(style_name), time)
144
145
 
145
146
  if @options[:fog_host]
146
- expiring_url.gsub!(/#{host_name_for_directory}/, dynamic_fog_host_for_style(style))
147
+ expiring_url.gsub!(/#{host_name_for_directory}/, dynamic_fog_host_for_style(style_name))
147
148
  end
148
149
  else
149
- expiring_url = public_url
150
+ expiring_url = url(style_name)
150
151
  end
151
152
 
152
153
  return expiring_url
@@ -171,6 +172,13 @@ module Paperclip
171
172
 
172
173
  private
173
174
 
175
+ def convert_time(time)
176
+ if time.is_a?(Fixnum)
177
+ time = Time.now + time
178
+ end
179
+ time
180
+ end
181
+
174
182
  def dynamic_fog_host_for_style(style)
175
183
  if @options[:fog_host].respond_to?(:call)
176
184
  @options[:fog_host].call(self)
@@ -6,7 +6,7 @@ module Paperclip
6
6
  # To use Paperclip with S3, include the +aws-sdk+ gem in your Gemfile:
7
7
  # gem 'aws-sdk'
8
8
  # There are a few S3-specific options for has_attached_file:
9
- # * +s3_credentials+: Takes a path, a File, or a Hash. The path (or File) must point
9
+ # * +s3_credentials+: Takes a path, a File, a Hash or a Proc. The path (or File) must point
10
10
  # to a YAML file containing the +access_key_id+ and +secret_access_key+ that Amazon
11
11
  # gives you. You can 'environment-space' this just like you do to your
12
12
  # database.yml file, so different environments can use different accounts:
@@ -26,9 +26,21 @@ module Paperclip
26
26
  # put your bucket name in this file, instead of adding it to the code directly.
27
27
  # This is useful when you want the same account but a different bucket for
28
28
  # development versus production.
29
+ # When using a Proc it provides a single parameter which is the attachment itself. A
30
+ # method #instance is available on the attachment which will take you back to your
31
+ # code. eg.
32
+ # class User
33
+ # has_attached_file :download,
34
+ # :storage => :s3,
35
+ # :s3_credentials => Proc.new{|a| a.instance.s3_credentials }
36
+ #
37
+ # def s3_credentials
38
+ # {:bucket => "xxx", :access_key_id => "xxx", :secret_access_key => "xxx"}
39
+ # end
40
+ # end
29
41
  # * +s3_permissions+: This is a String that should be one of the "canned" access
30
42
  # policies that S3 provides (more information can be found here:
31
- # http://docs.amazonwebservices.com/AmazonS3/latest/dev/index.html?RESTAccessPolicy.html)
43
+ # http://docs.aws.amazon.com/AmazonS3/latest/dev/ACLOverview.html)
32
44
  # The default for Paperclip is :public_read.
33
45
  #
34
46
  # You can set permission on a per style bases by doing the following:
@@ -137,8 +149,8 @@ module Paperclip
137
149
  @s3_server_side_encryption = @options[:s3_server_side_encryption].to_s.upcase
138
150
  end
139
151
 
140
- unless @options[:url].to_s.match(/^:s3.*url$/) || @options[:url] == ":asset_host"
141
- @options[:path] = @options[:path].gsub(/:url/, @options[:url]).gsub(/^:rails_root\/public\/system/, '')
152
+ unless @options[:url].to_s.match(/\A:s3.*url\Z/) || @options[:url] == ":asset_host"
153
+ @options[:path] = @options[:path].gsub(/:url/, @options[:url]).gsub(/\A:rails_root\/public\/system/, '')
142
154
  @options[:url] = ":s3_path_url"
143
155
  end
144
156
  @options[:url] = @options[:url].inspect if @options[:url].is_a?(Symbol)
@@ -147,25 +159,25 @@ module Paperclip
147
159
  end
148
160
 
149
161
  Paperclip.interpolates(:s3_alias_url) do |attachment, style|
150
- "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{^/}, "")}"
162
+ "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{\A/}, "")}"
151
163
  end unless Paperclip::Interpolations.respond_to? :s3_alias_url
152
164
  Paperclip.interpolates(:s3_path_url) do |attachment, style|
153
- "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_name}/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
165
+ "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_name}/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{\A/}, "")}"
154
166
  end unless Paperclip::Interpolations.respond_to? :s3_path_url
155
167
  Paperclip.interpolates(:s3_domain_url) do |attachment, style|
156
- "#{attachment.s3_protocol(style, true)}//#{attachment.bucket_name}.#{attachment.s3_host_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
168
+ "#{attachment.s3_protocol(style, true)}//#{attachment.bucket_name}.#{attachment.s3_host_name}/#{attachment.path(style).gsub(%r{\A/}, "")}"
157
169
  end unless Paperclip::Interpolations.respond_to? :s3_domain_url
158
170
  Paperclip.interpolates(:asset_host) do |attachment, style|
159
- "#{attachment.path(style).gsub(%r{^/}, "")}"
171
+ "#{attachment.path(style).gsub(%r{\A/}, "")}"
160
172
  end unless Paperclip::Interpolations.respond_to? :asset_host
161
173
  end
162
174
 
163
175
  def expiring_url(time = 3600, style_name = default_style)
164
- if path
176
+ if path(style_name)
165
177
  base_options = { :expires => time, :secure => use_secure_protocol?(style_name) }
166
178
  s3_object(style_name).url_for(:read, base_options.merge(s3_url_options)).to_s
167
179
  else
168
- url
180
+ url(style_name)
169
181
  end
170
182
  end
171
183
 
@@ -232,7 +244,7 @@ module Paperclip
232
244
  end
233
245
 
234
246
  def s3_object style_name = default_style
235
- s3_bucket.objects[path(style_name).sub(%r{^/},'')]
247
+ s3_bucket.objects[path(style_name).sub(%r{\A/},'')]
236
248
  end
237
249
 
238
250
  def using_http_proxy?
@@ -340,7 +352,7 @@ module Paperclip
340
352
  @queued_for_delete.each do |path|
341
353
  begin
342
354
  log("deleting #{path}")
343
- s3_bucket.objects[path.sub(%r{^/},'')].delete
355
+ s3_bucket.objects[path.sub(%r{\A/},'')].delete
344
356
  rescue AWS::Errors::Base => e
345
357
  # Ignore this.
346
358
  end
@@ -385,10 +397,10 @@ module Paperclip
385
397
  http_headers = http_headers.call(instance) if http_headers.respond_to?(:call)
386
398
  http_headers.inject({}) do |headers,(name,value)|
387
399
  case name.to_s
388
- when /^x-amz-meta-(.*)/i
400
+ when /\Ax-amz-meta-(.*)/i
389
401
  s3_metadata[$1.downcase] = value
390
402
  else
391
- s3_headers[name.to_s.downcase.sub(/^x-amz-/,'').tr("-","_").to_sym] = value
403
+ s3_headers[name.to_s.downcase.sub(/\Ax-amz-/,'').tr("-","_").to_sym] = value
392
404
  end
393
405
  end
394
406
  end
@@ -1,8 +1,6 @@
1
1
  module Paperclip
2
2
  class TempfileFactory
3
3
 
4
- ILLEGAL_FILENAME_CHARACTERS = /^~/
5
-
6
4
  def generate(name)
7
5
  @name = name
8
6
  file = Tempfile.new([basename, extension])
@@ -15,7 +13,7 @@ module Paperclip
15
13
  end
16
14
 
17
15
  def basename
18
- File.basename(@name, extension).gsub(ILLEGAL_FILENAME_CHARACTERS, '_')
16
+ Digest::MD5.hexdigest(File.basename(@name, extension))
19
17
  end
20
18
  end
21
19
  end
@@ -30,13 +30,15 @@ module Paperclip
30
30
  options = attributes.extract_options!.dup
31
31
 
32
32
  Paperclip::Validators.constants.each do |constant|
33
- if constant.to_s =~ /^Attachment(.+)Validator$/
33
+ if constant.to_s =~ /\AAttachment(.+)Validator\Z/
34
34
  validator_kind = $1.underscore.to_sym
35
35
 
36
36
  if options.has_key?(validator_kind)
37
37
  validator_options = options.delete(validator_kind)
38
38
  validator_options = {} if validator_options == true
39
39
  local_options = attributes + [validator_options]
40
+ conditional_options = options.slice(:if, :unless)
41
+ local_options.last.merge!(conditional_options)
40
42
  send(:"validates_attachment_#{validator_kind}", *local_options)
41
43
  end
42
44
  end
@@ -1,3 +1,3 @@
1
1
  module Paperclip
2
- VERSION = "3.5.2" unless defined? Paperclip::VERSION
2
+ VERSION = "3.5.3" unless defined? Paperclip::VERSION
3
3
  end
@@ -19,10 +19,6 @@ Gem::Specification.new do |s|
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = ["lib"]
21
21
 
22
- if File.exists?('UPGRADING')
23
- s.post_install_message = File.read("UPGRADING")
24
- end
25
-
26
22
  s.requirements << "ImageMagick"
27
23
  s.required_ruby_version = ">= 1.9.2"
28
24
 
@@ -103,7 +103,11 @@ module Paperclip
103
103
  end
104
104
  end
105
105
 
106
- if defined?(ActionController::Integration::Session)
106
+ if defined?(ActionDispatch::Integration::Session)
107
+ class ActionDispatch::IntegrationTest::Session #:nodoc:
108
+ include Paperclip::Shoulda
109
+ end
110
+ elsif defined?(ActionController::Integration::Session)
107
111
  class ActionController::Integration::Session #:nodoc:
108
112
  include Paperclip::Shoulda
109
113
  end
@@ -119,6 +123,13 @@ else
119
123
  end
120
124
  end
121
125
 
122
- class Test::Unit::TestCase #:nodoc:
123
- extend Paperclip::Shoulda
126
+ if defined?(Minitest)
127
+ class Minitest::Unit::TestCase #:nodoc:
128
+ extend Paperclip::Shoulda
129
+ end
130
+ elsif defined?(Test)
131
+ class Test::Unit::TestCase #:nodoc:
132
+ extend Paperclip::Shoulda
133
+ end
124
134
  end
135
+
@@ -62,6 +62,17 @@ class AttachmentRegistryTest < Test::Unit::TestCase
62
62
 
63
63
  assert_equal expected_definitions, definitions
64
64
  end
65
+
66
+ should "produce defintions for subclasses" do
67
+ expected_definitions = { avatar: { yo: 'greeting' } }
68
+ Foo = Class.new
69
+ Bar = Class.new(Foo)
70
+ Paperclip::AttachmentRegistry.register(Foo, :avatar, expected_definitions[:avatar])
71
+
72
+ definitions = Paperclip::AttachmentRegistry.definitions_for(Bar)
73
+
74
+ assert_equal expected_definitions, definitions
75
+ end
65
76
  end
66
77
 
67
78
  context '.clear' do
@@ -62,6 +62,26 @@ class AttachmentTest < Test::Unit::TestCase
62
62
  assert_file_exists(dummy.avatar.path(:original))
63
63
  end
64
64
 
65
+ context "having a not empty hash as a default option" do
66
+ setup do
67
+ @old_default_options = Paperclip::Attachment.default_options.dup
68
+ @new_default_options = { :convert_options => { :all => "-background white" } }
69
+ Paperclip::Attachment.default_options.merge!(@new_default_options)
70
+ end
71
+
72
+ teardown do
73
+ Paperclip::Attachment.default_options.merge!(@old_default_options)
74
+ end
75
+
76
+ should "deep merge when it is overridden" do
77
+ new_options = { :convert_options => { :thumb => "-thumbnailize" } }
78
+ attachment = Paperclip::Attachment.new(:name, :instance, new_options)
79
+
80
+ assert_equal Paperclip::Attachment.default_options.deep_merge(new_options),
81
+ attachment.instance_variable_get("@options")
82
+ end
83
+ end
84
+
65
85
  should "handle a boolean second argument to #url" do
66
86
  mock_url_generator_builder = MockUrlGeneratorBuilder.new
67
87
  attachment = Paperclip::Attachment.new(:name, :instance, :url_generator => mock_url_generator_builder)
@@ -643,7 +663,7 @@ class AttachmentTest < Test::Unit::TestCase
643
663
  rebuild_model :processors => [:thumbnail, :test], :styles => @style_params
644
664
  @dummy = Dummy.new
645
665
  @file = StringIO.new("...")
646
- @file.stubs(:to_tempfile).returns(@file)
666
+ @file.stubs(:close)
647
667
  Paperclip::Test.stubs(:make).returns(@file)
648
668
  Paperclip::Thumbnail.stubs(:make).returns(@file)
649
669
  end
@@ -1419,6 +1439,15 @@ class AttachmentTest < Test::Unit::TestCase
1419
1439
  @dummy.destroy
1420
1440
  assert_file_not_exists(@path)
1421
1441
  end
1442
+
1443
+ should "not be deleted when transaction rollbacks after model is destroyed" do
1444
+ ActiveRecord::Base.transaction do
1445
+ @dummy.destroy
1446
+ raise ActiveRecord::Rollback
1447
+ end
1448
+
1449
+ assert_file_exists(@path)
1450
+ end
1422
1451
  end
1423
1452
 
1424
1453
  end
@@ -8,6 +8,7 @@ class ContentTypeDetectorTest < Test::Unit::TestCase
8
8
  should 'return the empty content type when the file is empty' do
9
9
  tempfile = Tempfile.new("empty")
10
10
  assert_equal "inode/x-empty", Paperclip::ContentTypeDetector.new(tempfile.path).detect
11
+ tempfile.close
11
12
  end
12
13
 
13
14
  should 'return content type of file if it is an acceptable type' do
@@ -7,6 +7,8 @@ class FileCommandContentTypeDetectorTest < Test::Unit::TestCase
7
7
  tempfile.rewind
8
8
 
9
9
  assert_equal "text/plain", Paperclip::FileCommandContentTypeDetector.new(tempfile.path).detect
10
+
11
+ tempfile.close
10
12
  end
11
13
 
12
14
  should 'return a sensible default when the file command is missing' do
@@ -72,8 +72,12 @@ class GeneratorTest < Rails::Generators::TestCase
72
72
 
73
73
  context 'without required arguments' do
74
74
  should 'not create the migration' do
75
- silence_stream(STDERR) { run_generator %w() }
76
- assert_no_migration 'db/migrate/add_attachment_avatar_to_users.rb'
75
+ begin
76
+ silence_stream(STDERR) { run_generator %w() }
77
+ assert_no_migration 'db/migrate/add_attachment_avatar_to_users.rb'
78
+ rescue Thor::RequiredArgumentMissingError
79
+ # This is also OK. It happens in 1.9.2 and Rails 3.2
80
+ end
77
81
  end
78
82
  end
79
83
  end
@@ -35,8 +35,8 @@ class HasAttachedFileTest < Test::Unit::TestCase
35
35
  assert_adding_attachment('avatar').defines_callback('before_destroy')
36
36
  end
37
37
 
38
- should 'define an after_destroy callback' do
39
- assert_adding_attachment('avatar').defines_callback('after_destroy')
38
+ should 'define an after_commit callback' do
39
+ assert_adding_attachment('avatar').defines_callback('after_commit')
40
40
  end
41
41
 
42
42
  should 'define the Paperclip-specific callbacks' do
@@ -116,7 +116,7 @@ class HasAttachedFileTest < Test::Unit::TestCase
116
116
  define_method: nil,
117
117
  after_save: nil,
118
118
  before_destroy: nil,
119
- after_destroy: nil,
119
+ after_commit: nil,
120
120
  define_paperclip_callbacks: nil,
121
121
  extend: nil,
122
122
  name: 'Billy')
@@ -26,12 +26,13 @@ puts "Testing against version #{ActiveRecord::VERSION::STRING}"
26
26
 
27
27
  begin
28
28
  require 'ruby-debug'
29
- rescue LoadError => e
29
+ rescue LoadError
30
30
  puts "debugger disabled"
31
31
  end
32
32
 
33
33
  ROOT = Pathname(File.expand_path(File.join(File.dirname(__FILE__), '..')))
34
34
 
35
+ $previous_count = 0
35
36
  class Test::Unit::TestCase
36
37
  def setup
37
38
  silence_warnings do
@@ -41,6 +42,22 @@ class Test::Unit::TestCase
41
42
  Rails.stubs(:const_defined?).with(:Railtie).returns(false)
42
43
  end
43
44
  end
45
+
46
+ def teardown
47
+ end
48
+
49
+ def report_files
50
+ files = []
51
+ ObjectSpace.each_object(IO){|io| files << io unless io.closed? }
52
+ if files.count > $previous_count
53
+ puts __name__
54
+ puts "#{files.count} files"
55
+ files.each do |file|
56
+ puts "Open IO: #{file.inspect}"
57
+ end
58
+ end
59
+ $previous_count = files.count
60
+ end
44
61
  end
45
62
 
46
63
  $LOAD_PATH << File.join(ROOT, 'lib')
@@ -17,8 +17,7 @@ class IntegrationTest < Test::Unit::TestCase
17
17
 
18
18
  should "not exceed the open file limit" do
19
19
  assert_nothing_raised do
20
- dummies = Dummy.find(:all)
21
- dummies.each { |dummy| dummy.avatar }
20
+ Dummy.all.each { |dummy| dummy.avatar }
22
21
  end
23
22
  end
24
23
  end
@@ -35,7 +34,7 @@ class IntegrationTest < Test::Unit::TestCase
35
34
  teardown { @file.close }
36
35
 
37
36
  should "create its thumbnails properly" do
38
- assert_match /\b50x50\b/, `identify "#{@dummy.avatar.path(:thumb)}"`
37
+ assert_match(/\b50x50\b/, `identify "#{@dummy.avatar.path(:thumb)}"`)
39
38
  end
40
39
 
41
40
  context 'reprocessing with unreadable original' do
@@ -70,8 +69,8 @@ class IntegrationTest < Test::Unit::TestCase
70
69
  end
71
70
 
72
71
  should "create its thumbnails properly" do
73
- assert_match /\b150x25\b/, `identify "#{@dummy.avatar.path(:thumb)}"`
74
- assert_match /\b50x50\b/, `identify "#{@dummy.avatar.path(:dynamic)}"`
72
+ assert_match(/\b150x25\b/, `identify "#{@dummy.avatar.path(:thumb)}"`)
73
+ assert_match(/\b50x50\b/, `identify "#{@dummy.avatar.path(:dynamic)}"`)
75
74
  end
76
75
 
77
76
  should "change the timestamp" do