letter_opener 1.7.0 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ef387f446fe58d7fbe039333d493cc644dbb388323e098f2b4a0a48dfad93ac
4
- data.tar.gz: 3d6da1bcc7fa1d575e00d361677e36a73dfd26ea331416cfbb31c14cdd8f87ac
3
+ metadata.gz: 3b5ce93130e74aeaf3c51e330b1cdeb039fe8161d40a832792af5da3bbc46d4d
4
+ data.tar.gz: 01bfe8f9f88f2a1f5db6f68c3a28d829bb7984b586e5a3e4bec0e471e7b54371
5
5
  SHA512:
6
- metadata.gz: 62fc742943f4904086cfdddcd0908c7bb67fd602530e340852eb213644bce32e664e6da00058767fd80cfc8f13735e757e2d4fa32cb4459be74df8bdf7348030
7
- data.tar.gz: 827cb033ed7805de3399f2c32427e69190a15420314d2f4ad6b4f3856a8209d6cbf0b0d5701ea6e47dd996fd919ef5e9185389df3b151ce24b3ad67a693492a6
6
+ metadata.gz: 32e5d64b76e25905b511eaa939aa56cbeb0f1741005912ed8f7b35d89eef03b86ece4e9bab5cc8f859ae7bdbaa8ce9ec99483b1dee9d4bf566d42496b8452de4
7
+ data.tar.gz: 3df336bcbab473c8f15c15dee759d78f57403b1fb1026e2aa82cfd1cf40e0bcd3648506007484a104263e222c6ac97945a0ce8768b0e3cd920ca00031ff1863d
data/CHANGELOG.md CHANGED
@@ -1,4 +1,11 @@
1
- ## master ##
1
+ ## 1.8.0 ##
2
+ * Allow configuration of the 'file///' part in Launchy.open path (thanks [
3
+ Regis Millet](https://github.com/Kulgar))
4
+ * Enhance 'tmp:clear' task to delete 'tmp/letter_opener' files (thanks [Joaquín Vicente](https://github.com/wacko))
5
+ * Convert mail's subject to UTF-8. (thanks [kuroponzu](https://github.com/kuroponzu))
6
+ * Use proper attachment's filename sanitization so we don't escape Japanese characters.
7
+
8
+ ## 1.7.0 ##
2
9
  * Use default configuration in `Message::rendered_messages` (thanks [Krystan HuffMenne
3
10
  ](https://github.com/gitKrystan))
4
11
  * Do not use `Rails.root` path if LetterOpener is used outside of Rails (thanks [centrevillage](https://github.com/centrevillage))
@@ -80,4 +87,4 @@
80
87
 
81
88
  ## 0.0.1 ##
82
89
 
83
- * Initial relase
90
+ * Initial release
data/Gemfile CHANGED
@@ -1,4 +1,10 @@
1
1
  source "https://rubygems.org"
2
2
  gemspec
3
3
 
4
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.1")
5
+ gem "net-imap", require: false
6
+ gem "net-pop", require: false
7
+ gem "net-smtp", require: false
8
+ end
9
+
4
10
  gem "rake"
data/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # Letter Opener [![Ruby](https://github.com/ryanb/letter_opener/actions/workflows/ruby.yml/badge.svg)](https://github.com/ryanb/letter_opener/actions/workflows/ruby.yml)
2
+
3
+ Preview email in the default browser instead of sending it. This means you do not need to set up email delivery in your development environment, and you no longer need to worry about accidentally sending a test email to someone else's address.
4
+
5
+ ## Rails Setup
6
+
7
+ First add the gem to your development environment and run the `bundle` command to install it.
8
+
9
+ ```rb
10
+ gem "letter_opener", group: :development
11
+ ```
12
+
13
+ Then set the delivery method in `config/environments/development.rb`
14
+
15
+ ```rb
16
+ config.action_mailer.delivery_method = :letter_opener
17
+ config.action_mailer.perform_deliveries = true
18
+ ```
19
+
20
+ Now any email will pop up in your browser instead of being sent. The messages are stored in `tmp/letter_opener`.
21
+ If you want to change application that will be used to open your emails you should override `LAUNCHY_APPLICATION` environment variable or set `Launchy.application` in the initializer.
22
+
23
+ ### Configuration
24
+
25
+ ```rb
26
+ LetterOpener.configure do |config|
27
+ # To overrider the location for message storage.
28
+ # Default value is `tmp/letter_opener`
29
+ config.location = Rails.root.join('tmp', 'my_mails')
30
+
31
+ # To render only the message body, without any metadata or extra containers or styling.
32
+ # Default value is `:default` that renders styled message with showing useful metadata.
33
+ config.message_template = :light
34
+
35
+ # To change default file URI scheme you can provide `file_uri_scheme` config.
36
+ # It might be useful when you use WSL (Windows Subsystem for Linux) and default
37
+ # scheme doesn't work for you.
38
+ # Default value is blank
39
+ config.file_uri_scheme = 'file://///wsl$/Ubuntu-18.04'
40
+ end
41
+ ```
42
+
43
+ ### For Rails 2.3.x support
44
+
45
+ There is a fork that add support for Rails 2.3.x, in order to use that or just check it out you should go to https://github.com/cavi21/letter_opener
46
+
47
+ ## Non Rails Setup
48
+
49
+ If you aren't using Rails, this can be easily set up with the Mail gem. Just set the delivery method when configuring Mail and specify a location.
50
+
51
+ ```rb
52
+ require "letter_opener"
53
+ Mail.defaults do
54
+ delivery_method LetterOpener::DeliveryMethod, location: File.expand_path('../tmp/letter_opener', __FILE__)
55
+ end
56
+ ```
57
+
58
+ The method is similar if you're using the Pony gem:
59
+
60
+ ```rb
61
+ require "letter_opener"
62
+ Pony.options = {
63
+ via: LetterOpener::DeliveryMethod,
64
+ via_options: {location: File.expand_path('../tmp/letter_opener', __FILE__)}
65
+ }
66
+ ```
67
+
68
+ Alternatively, if you are using ActionMailer directly (without Rails) you will need to add the delivery method.
69
+
70
+ ```rb
71
+ require "letter_opener"
72
+ ActionMailer::Base.add_delivery_method :letter_opener, LetterOpener::DeliveryMethod, :location => File.expand_path('../tmp/letter_opener', __FILE__)
73
+ ActionMailer::Base.delivery_method = :letter_opener
74
+ ```
75
+
76
+ ## Remote Alternatives
77
+
78
+ Letter Opener uses [Launchy](https://github.com/copiousfreetime/launchy) to open sent mail in the browser. This assumes the Ruby process is running on the local development machine. If you are using a separate staging server or VM this will not work. In that case consider using [Mailtrap](http://mailtrap.io/) or [MailCatcher](http://mailcatcher.me/).
79
+
80
+ If you are running your application within a Docker Container or VM and do not have a browser available to open emails received by Letter Opener, you may see the following error:
81
+
82
+ ```
83
+ WARN: Launchy::CommandNotFoundError: Unable to find a browser command. If this is unexpected, Please rerun with environment variable LAUNCHY_DEBUG=true or the '-d' commandline option and file a bug at https://github.com/copiousfreetime/launchy/issues/new
84
+ ```
85
+
86
+ To resolve this, simply set the following ENV variables:
87
+
88
+ ```
89
+ LAUNCHY_DRY_RUN=true
90
+ BROWSER=/dev/null
91
+ ```
92
+
93
+ In order to keep this project simple, I don't have plans to turn it into a Rails engine with an interface for browsing the sent mail but there is a [gem you can use for that](https://github.com/fgrehm/letter_opener_web).
94
+
95
+
96
+ ## Development & Feedback
97
+
98
+ Questions or problems? Please use the [issue tracker](https://github.com/ryanb/letter_opener/issues). If you would like to contribute to this project, fork this repository and run `bundle` and `rake` to run the tests. Pull requests appreciated.
99
+
100
+ Special thanks to the [mail_view](https://github.com/37signals/mail_view/) gem for inspiring this project and for their mail template. Also thanks to [Vasiliy Ermolovich](https://github.com/nashby) for helping manage this project.
@@ -1,10 +1,11 @@
1
1
  module LetterOpener
2
2
  class Configuration
3
- attr_accessor :location, :message_template
3
+ attr_accessor :location, :message_template, :file_uri_scheme
4
4
 
5
5
  def initialize
6
- @location = Rails.root.join('tmp', 'letter_opener') if defined?(Rails) && Rails.respond_to?(:root)
6
+ @location = Rails.root.join('tmp', 'letter_opener') if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
7
7
  @message_template = 'default'
8
+ @file_uri_scheme = nil
8
9
  end
9
10
  end
10
11
  end
@@ -10,6 +10,7 @@ module LetterOpener
10
10
  def initialize(options = {})
11
11
  options[:message_template] ||= LetterOpener.configuration.message_template
12
12
  options[:location] ||= LetterOpener.configuration.location
13
+ options[:file_uri_scheme] ||= LetterOpener.configuration.file_uri_scheme
13
14
 
14
15
  raise InvalidOption, "A location option is required when using the Letter Opener delivery method" if options[:location].nil?
15
16
 
@@ -21,7 +22,7 @@ module LetterOpener
21
22
  location = File.join(settings[:location], "#{Time.now.to_f.to_s.tr('.', '_')}_#{Digest::SHA1.hexdigest(mail.encoded)[0..6]}")
22
23
 
23
24
  messages = Message.rendered_messages(mail, location: location, message_template: settings[:message_template])
24
- Launchy.open("file:///#{messages.first.filepath}")
25
+ ::Launchy.open("#{settings[:file_uri_scheme]}#{messages.first.filepath}")
25
26
  end
26
27
 
27
28
  private
@@ -2,6 +2,7 @@ require "cgi"
2
2
  require "erb"
3
3
  require "fileutils"
4
4
  require "uri"
5
+ require "kconv"
5
6
 
6
7
  module LetterOpener
7
8
  class Message
@@ -43,7 +44,7 @@ module LetterOpener
43
44
  File.open(path, 'wb') { |f| f.write(attachment.body.raw_source) }
44
45
  end
45
46
 
46
- @attachments << [attachment.filename, "attachments/#{CGI.escape(filename)}"]
47
+ @attachments << [attachment.filename, "attachments/#{filename}"]
47
48
  end
48
49
  end
49
50
 
@@ -84,6 +85,10 @@ module LetterOpener
84
85
  @sender ||= Array(@mail['sender']).join(", ")
85
86
  end
86
87
 
88
+ def subject
89
+ @subject ||= @mail.subject.toutf8
90
+ end
91
+
87
92
  def to
88
93
  @to ||= Array(@mail['to']).join(", ")
89
94
  end
@@ -119,7 +124,10 @@ module LetterOpener
119
124
  end
120
125
 
121
126
  def attachment_filename(attachment)
122
- attachment.filename.gsub(/[^\w\-.]/, '_')
127
+ # Copied from https://github.com/rails/rails/blob/6bfc637659248df5d6719a86d2981b52662d9b50/activestorage/app/models/active_storage/filename.rb#L57
128
+ attachment.filename.encode(
129
+ Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/\t\r\n\\", "-"
130
+ )
123
131
  end
124
132
 
125
133
  def <=>(other)
@@ -8,5 +8,9 @@ module LetterOpener
8
8
  )
9
9
  end
10
10
  end
11
+
12
+ rake_tasks do
13
+ load 'letter_opener/tasks/letter_opener.rake'
14
+ end
11
15
  end
12
16
  end
@@ -0,0 +1,9 @@
1
+ require 'rails/tasks'
2
+
3
+ namespace :tmp do
4
+ task :letter_opener do
5
+ rm_rf Dir["tmp/letter_opener/[^.]*"], verbose: false
6
+ end
7
+ end
8
+
9
+ Rake::Task['tmp:clear'].enhance(['tmp:letter_opener'])
@@ -1,7 +1,8 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe LetterOpener::DeliveryMethod do
4
- let(:location) { File.expand_path('../../../tmp/letter_opener', __FILE__) }
4
+ let(:location) { File.expand_path('../../../tmp/letter_opener', __FILE__) }
5
+ let(:file_uri_scheme) { nil }
5
6
 
6
7
  let(:plain_file) { Dir["#{location}/*/plain.html"].first }
7
8
  let(:plain) { CGI.unescape_html(File.read(plain_file)) }
@@ -12,7 +13,7 @@ describe LetterOpener::DeliveryMethod do
12
13
  context = self
13
14
 
14
15
  Mail.defaults do
15
- delivery_method LetterOpener::DeliveryMethod, :location => context.location
16
+ delivery_method LetterOpener::DeliveryMethod, location: context.location, file_uri_scheme: context.file_uri_scheme
16
17
  end
17
18
  end
18
19
 
@@ -282,19 +283,18 @@ describe LetterOpener::DeliveryMethod do
282
283
  end
283
284
 
284
285
  it 'creates attachments dir with attachment' do
285
- attachment = Dir["#{location}/*/attachments/non_word_chars_used_01-02.txt"].first
286
+ attachment = Dir["#{location}/*/attachments/non word-chars-used,01-02.txt"].first
286
287
  expect(File.exist?(attachment)).to be_truthy
287
288
  end
288
289
 
289
290
  it 'saves attachment name' do
290
291
  plain = File.read(Dir["#{location}/*/plain.html"].first)
291
- expect(plain).to include('non_word_chars_used_01-02.txt')
292
+ expect(plain).to include('non word-chars-used,01-02.txt')
292
293
  end
293
294
 
294
295
  it 'replaces inline attachment names' do
295
296
  text = File.read(Dir["#{location}/*/rich.html"].first)
296
- expect(text).to_not include('attachments/non word:chars/used,01-02.txt')
297
- expect(text).to include('attachments/non_word_chars_used_01-02.txt')
297
+ expect(text).to include('attachments/non word-chars-used,01-02.txt')
298
298
  end
299
299
  end
300
300
 
@@ -373,4 +373,44 @@ describe LetterOpener::DeliveryMethod do
373
373
  expect(File.exist?(plain_file)).to be_truthy
374
374
  end
375
375
  end
376
+
377
+ context 'specifying custom file_uri_scheme configuration option' do
378
+ after do
379
+ Mail.defaults do
380
+ delivery_method LetterOpener::DeliveryMethod, location: File.expand_path('../../../tmp/letter_opener', __FILE__)
381
+ end
382
+
383
+ Mail.deliver do
384
+ subject 'Foo subject'
385
+ from 'Foo foo@example.com'
386
+ reply_to 'No Reply no-reply@example.com'
387
+ to 'Bar bar@example.com'
388
+ body 'World! http://example.com'
389
+ end
390
+
391
+ LetterOpener.configure do |config|
392
+ config.file_uri_scheme = file_uri_scheme
393
+ end
394
+ end
395
+
396
+ context 'file_uri_scheme is not set in configuration' do
397
+ it "sends the path to Launchy with the 'file://' prefix by default" do
398
+ allow(Launchy).to receive(:open) do |path|
399
+ expect(path).not_to match(/^file:\/\//)
400
+ end
401
+ end
402
+ end
403
+
404
+ context 'file_uri_scheme is set in configuration' do
405
+ it "sends the path to Launchy with the 'file://///wsl$/Ubuntu-18.04' prefix" do
406
+ allow(Launchy).to receive(:open) do |path|
407
+ expect(path).to match(/^file:\/\/\/\/\/wsl\$\/Ubuntu-18.04/)
408
+ end
409
+
410
+ LetterOpener.configure do |config|
411
+ config.file_uri_scheme = 'file://///wsl$/Ubuntu-18.04'
412
+ end
413
+ end
414
+ end
415
+ end
376
416
  end
@@ -35,6 +35,20 @@ describe LetterOpener::Message do
35
35
 
36
36
  end
37
37
 
38
+ describe '#subject' do
39
+ it 'handles UTF-8 charset subject' do
40
+ mail = mail(:subject => 'test_mail')
41
+ message = described_class.new(mail, location: location)
42
+ expect(message.subject).to eq('test_mail')
43
+ end
44
+
45
+ it 'handles encode ISO-2022-JP charset subject' do
46
+ mail = mail(:subject => '=?iso-2022-jp?B?GyRCJUYlOSVIJWEhPCVrGyhC?=')
47
+ message = described_class.new(mail, location: location)
48
+ expect(message.subject).to eq('テストメール')
49
+ end
50
+ end
51
+
38
52
  describe '#to' do
39
53
  it 'handles one email as a string' do
40
54
  mail = mail(:to => 'test@example.com')
metadata CHANGED
@@ -1,43 +1,49 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: letter_opener
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Bates
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-12-10 00:00:00.000000000 Z
11
+ date: 2022-03-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: launchy
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '2.2'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '3'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
25
28
  - !ruby/object:Gem::Version
26
29
  version: '2.2'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '3'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: rspec
29
35
  requirement: !ruby/object:Gem::Requirement
30
36
  requirements:
31
37
  - - "~>"
32
38
  - !ruby/object:Gem::Version
33
- version: 3.5.0
39
+ version: 3.10.0
34
40
  type: :development
35
41
  prerelease: false
36
42
  version_requirements: !ruby/object:Gem::Requirement
37
43
  requirements:
38
44
  - - "~>"
39
45
  - !ruby/object:Gem::Version
40
- version: 3.5.0
46
+ version: 3.10.0
41
47
  - !ruby/object:Gem::Dependency
42
48
  name: mail
43
49
  requirement: !ruby/object:Gem::Requirement
@@ -62,22 +68,28 @@ files:
62
68
  - CHANGELOG.md
63
69
  - Gemfile
64
70
  - LICENSE
65
- - README.rdoc
71
+ - README.md
66
72
  - Rakefile
67
73
  - lib/letter_opener.rb
68
74
  - lib/letter_opener/configuration.rb
69
75
  - lib/letter_opener/delivery_method.rb
70
76
  - lib/letter_opener/message.rb
71
77
  - lib/letter_opener/railtie.rb
78
+ - lib/letter_opener/tasks/letter_opener.rake
72
79
  - lib/letter_opener/templates/default.html.erb
73
80
  - lib/letter_opener/templates/light.html.erb
74
81
  - spec/letter_opener/delivery_method_spec.rb
75
82
  - spec/letter_opener/message_spec.rb
76
83
  - spec/spec_helper.rb
77
- homepage: http://github.com/ryanb/letter_opener
84
+ homepage: https://github.com/ryanb/letter_opener
78
85
  licenses:
79
86
  - MIT
80
- metadata: {}
87
+ metadata:
88
+ bug_tracker_uri: https://github.com/ryanb/letter_opener/issues
89
+ changelog_uri: https://github.com/ryanb/letter_opener/blob/master/CHANGELOG.md
90
+ documentation_uri: http://www.rubydoc.info/gems/letter_opener/
91
+ homepage_uri: https://github.com/ryanb/letter_opener
92
+ source_code_uri: https://github.com/ryanb/letter_opener/
81
93
  post_install_message:
82
94
  rdoc_options: []
83
95
  require_paths:
@@ -93,8 +105,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
93
105
  - !ruby/object:Gem::Version
94
106
  version: 1.3.4
95
107
  requirements: []
96
- rubyforge_project: letter_opener
97
- rubygems_version: 2.7.8
108
+ rubygems_version: 3.0.9
98
109
  signing_key:
99
110
  specification_version: 4
100
111
  summary: Preview mail in browser instead of sending.
data/README.rdoc DELETED
@@ -1,71 +0,0 @@
1
- = Letter Opener {<img src="https://secure.travis-ci.org/ryanb/letter_opener.png" />}[http://travis-ci.org/ryanb/letter_opener]
2
-
3
- Preview email in the default browser instead of sending it. This means you do not need to set up email delivery in your development environment, and you no longer need to worry about accidentally sending a test email to someone else's address.
4
-
5
-
6
- == Rails Setup
7
-
8
- First add the gem to your development environment and run the +bundle+ command to install it.
9
-
10
- gem "letter_opener", :group => :development
11
-
12
- Then set the delivery method in <tt>config/environments/development.rb</tt>
13
-
14
- config.action_mailer.delivery_method = :letter_opener
15
- config.action_mailer.perform_deliveries = true
16
-
17
- Now any email will pop up in your browser instead of being sent. The messages are stored in <tt>tmp/letter_opener</tt>.
18
- If you want to change application that will be used to open your emails you should override <tt>LAUNCHY_APPLICATION</tt> environment variable or set <tt>Launchy.application</tt> in the initializer.
19
-
20
- ==== Configuration
21
-
22
- LetterOpener.configure do |config|
23
- # To overrider the location for message storage.
24
- # Default value is <tt>tmp/letter_opener</tt>
25
- config.location = Rails.root.join('tmp', 'my_mails')
26
-
27
- # To render only the message body, without any metadata or extra containers or styling.
28
- # Default value is <tt>:default</tt> that renders styled message with showing useful metadata.
29
- config.message_template = :light
30
- end
31
-
32
- ==== For Rails 2.3.x support
33
-
34
- There is a fork that add support for Rails 2.3.x, in order to use that or just check it out you should go to https://github.com/cavi21/letter_opener
35
-
36
- == Non Rails Setup
37
-
38
- If you aren't using Rails, this can be easily set up with the Mail gem. Just set the delivery method when configuring Mail and specify a location.
39
-
40
- require "letter_opener"
41
- Mail.defaults do
42
- delivery_method LetterOpener::DeliveryMethod, :location => File.expand_path('../tmp/letter_opener', __FILE__)
43
- end
44
-
45
- The method is similar if you're using the Pony gem:
46
-
47
- require "letter_opener"
48
- Pony.options = {
49
- :via => LetterOpener::DeliveryMethod,
50
- :via_options => {:location => File.expand_path('../tmp/letter_opener', __FILE__)}
51
- }
52
-
53
- Alternatively, if you are using ActionMailer directly (without Rails) you will need to add the delivery method.
54
-
55
- require "letter_opener"
56
- ActionMailer::Base.add_delivery_method :letter_opener, LetterOpener::DeliveryMethod, :location => File.expand_path('../tmp/letter_opener', __FILE__)
57
- ActionMailer::Base.delivery_method = :letter_opener
58
-
59
-
60
- == Remote Alternatives
61
-
62
- Letter Opener uses {Launchy}[https://github.com/copiousfreetime/launchy] to open sent mail in the browser. This assumes the Ruby process is running on the local development machine. If you are using a separate staging server or VM this will not work. In that case consider using {Mailtrap}[http://mailtrap.io/] or {MailCatcher}[http://mailcatcher.me/].
63
-
64
- In order to keep this project simple, I don't have plans to turn it into a Rails engine with an interface for browsing the sent mail but there is a {gem you can use for that}[https://github.com/fgrehm/letter_opener_web].
65
-
66
-
67
- == Development & Feedback
68
-
69
- Questions or problems? Please use the {issue tracker}[https://github.com/ryanb/letter_opener/issues]. If you would like to contribute to this project, fork this repository and run +bundle+ and +rake+ to run the tests. Pull requests appreciated.
70
-
71
- Special thanks to the {mail_view}[https://github.com/37signals/mail_view/] gem for inspiring this project and for their mail template. Also thanks to {Vasiliy Ermolovich}[https://github.com/nashby] for helping manage this project.