Commit 7b754e5
Changed files (18)
.github
workflows
exe
lib
spec
.github/workflows/ci.yml
@@ -0,0 +1,26 @@
+name: ci
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '2.6'
+ bundler-cache: true
+ - run: ./bin/setup
+ - run: ./bin/test
+ style:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '2.6'
+ - run: ./bin/setup
+ - run: ./bin/style
bin/console
@@ -1,8 +1,8 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
-require 'bundler/setup'
-require 'minbox'
+require "bundler/setup"
+require "minbox"
# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.
@@ -11,5 +11,5 @@ require 'minbox'
# require "pry"
# Pry.start
-require 'irb'
+require "irb"
IRB.start(__FILE__)
bin/lint → bin/style
File renamed without changes
exe/minbox
@@ -1,6 +1,6 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
-require 'minbox/cli'
+require "minbox/cli"
Minbox::Cli::Application.start(ARGV)
lib/minbox/cli.rb
@@ -1,33 +1,33 @@
# frozen_string_literal: true
-require 'mail'
-require 'net/smtp'
-require 'openssl'
-require 'thor'
+require "mail"
+require "net/smtp"
+require "openssl"
+require "thor"
-require 'minbox'
+require "minbox"
module Minbox
module Cli
class Application < Thor
- package_name 'minbox'
+ package_name "minbox"
- method_option :from, type: :string, default: 'me@example.org'
- method_option :to, type: :string, default: ['them@example.org']
+ method_option :from, type: :string, default: "me@example.org"
+ method_option :to, type: :string, default: ["them@example.org"]
method_option :subject, type: :string, default: "#{Time.now} This is a test message."
method_option :body, type: :string, default: "#{Time.now} This is a test message."
- desc 'send <HOST> <PORT>', 'Send mail to SMTP server'
- def send(host = 'localhost', port = 25)
+ desc "send <HOST> <PORT>", "Send mail to SMTP server"
+ def send(host = "localhost", port = 25)
Net::SMTP.start(host, port) do |smtp|
smtp.debug_output = Minbox.logger
smtp.send_message(create_mail(options).to_s, options[:from], options[:to])
end
end
- method_option :output, type: :array, default: ['stdout']
+ method_option :output, type: :array, default: ["stdout"]
method_option :tls, type: :boolean, default: false
- desc 'server <HOST> <PORT>', 'SMTP server'
- def server(host = 'localhost', port = '25')
+ desc "server <HOST> <PORT>", "SMTP server"
+ def server(host = "localhost", port = "25")
publisher = Publisher.from(options[:output])
server = Server.new(host: host, port: port, tls: options[:tls])
server.listen! do |mail|
@@ -35,7 +35,7 @@ module Minbox
end
end
- desc 'version', 'Display the current version'
+ desc "version", "Display the current version"
def version
say Minbox::VERSION
end
lib/minbox/client.rb
@@ -3,62 +3,62 @@
module Minbox
class Ehlo
def run(client, line)
- _ehlo, _client_domain = line.split(' ')
+ _ehlo, _client_domain = line.split(" ")
client.write "250-#{client.server.host} offers a warm hug of welcome"
- client.write '250-8BITMIME'
- client.write '250-ENHANCEDSTATUSCODES'
+ client.write "250-8BITMIME"
+ client.write "250-ENHANCEDSTATUSCODES"
# client.write "250 STARTTLS"
- client.write '250-AUTH PLAIN LOGIN'
- client.write '250 OK'
+ client.write "250-AUTH PLAIN LOGIN"
+ client.write "250 OK"
end
end
class Helo
def run(client, line)
- _ehlo, _client_domain = line.split(' ')
+ _ehlo, _client_domain = line.split(" ")
client.write "250 #{client.server.host}"
end
end
class Noop
def run(client, _line)
- client.write '250 OK'
+ client.write "250 OK"
end
end
class Quit
def run(client, _line)
- client.write '221 Bye'
+ client.write "221 Bye"
client.close
end
end
class Data
def run(client, _line)
- client.write '354 End data with <CR><LF>.<CR><LF>'
+ client.write "354 End data with <CR><LF>.<CR><LF>"
body = []
line = client.read
until line.nil? || line.match(/^\.\r\n$/)
body << line
line = client.read
end
- client.write '250 OK'
+ client.write "250 OK"
yield(Mail.new(body.join)) unless body.empty?
end
end
class StartTls
def run(client, _line)
- client.write '220 Ready to start TLS'
+ client.write "220 Ready to start TLS"
client.secure_socket!
end
end
class AuthPlain
def run(client, line)
- data = line.gsub(/AUTH PLAIN ?/i, '')
- if data.strip == ''
- client.write '334'
+ data = line.gsub(/AUTH PLAIN ?/i, "")
+ if data.strip == ""
+ client.write "334"
data = client.read
end
parts = Base64.decode64(data).split("\0")
@@ -70,12 +70,12 @@ module Minbox
class AuthLogin
def run(client, line)
- username = line.gsub!(/AUTH LOGIN ?/i, '')
- if username.strip == ''
- client.write '334 VXNlcm5hbWU6'
+ username = line.gsub!(/AUTH LOGIN ?/i, "")
+ if username.strip == ""
+ client.write "334 VXNlcm5hbWU6"
username = client.read
end
- client.write '334 UGFzc3dvcmQ6'
+ client.write "334 UGFzc3dvcmQ6"
password = Base64.decode64(client.read)
client.authenticate(username, password)
end
@@ -84,7 +84,7 @@ module Minbox
class Unsupported
def run(client, line)
client.logger.error(line)
- client.write '502 Invalid/unsupported command'
+ client.write "502 Invalid/unsupported command"
end
end
@@ -152,9 +152,9 @@ module Minbox
def authenticate(username, password)
logger.debug("#{username}:#{password}")
- return write '535 Authenticated failed - protocol error' unless username && password
+ return write "535 Authenticated failed - protocol error" unless username && password
- write '235 2.7.0 Authentication successful'
+ write "235 2.7.0 Authentication successful"
end
end
end
lib/minbox/inbox.rb
@@ -16,10 +16,8 @@ module Minbox
end
def emails(count: 0)
- wait_until { |x| x.count >= count } if count > 0
- with_lock do |emails|
- emails.values
- end
+ wait_until { |x| x.count >= count } if count.positive?
+ with_lock(&:values)
end
def wait_until(seconds: 10, wait: 0.1)
@@ -52,14 +50,14 @@ module Minbox
end
def each
- @emails.each do |id, email|
+ @emails.each do |_id, email|
yield email
end
end
private
- def changed(modified, added, removed)
+ def changed(_modified, added, removed)
with_lock do |emails|
added.each do |file|
mail = Mail.read(file)
lib/minbox/publisher.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'redis'
+require "redis"
module Minbox
class LogPublisher
@@ -19,7 +19,7 @@ module Minbox
end
def publish(mail)
- @redis.publish('minbox', mail.to_s)
+ @redis.publish("minbox", mail.to_s)
end
end
@@ -27,7 +27,7 @@ module Minbox
attr_reader :dir
def initialize(dir = Dir.pwd)
- @dir = File.join(dir, 'tmp')
+ @dir = File.join(dir, "tmp")
FileUtils.mkdir_p(@dir)
end
lib/minbox/server.rb
@@ -2,10 +2,10 @@
module Minbox
class Server
- SUBJECT = '/C=CA/ST=AB/L=Calgary/O=minbox/OU=development/CN=minbox'
+ SUBJECT = "/C=CA/ST=AB/L=Calgary/O=minbox/OU=development/CN=minbox"
attr_reader :host, :logger, :key, :server
- def initialize(host: 'localhost', port: 25, tls: false, logger: Minbox.logger, thread_pool: Concurrent::CachedThreadPool.new)
+ def initialize(host: "localhost", port: 25, tls: false, logger: Minbox.logger, thread_pool: Concurrent::CachedThreadPool.new)
@host = host
@logger = logger
@tls = tls
@@ -21,7 +21,7 @@ module Minbox
def listen!(&block)
@server = upgrade(@server) if tls?
- logger.debug('Server started!')
+ logger.debug("Server started!")
loop do
handle(server.accept, &block)
@@ -78,8 +78,8 @@ module Minbox
extensions.subject_certificate =
extensions.issuer_certificate = certificate
[
- ['subjectKeyIdentifier', 'hash', false],
- ['keyUsage', 'keyEncipherment,digitalSignature', true],
+ ["subjectKeyIdentifier", "hash", false],
+ ["keyUsage", "keyEncipherment,digitalSignature", true],
].each do |x|
certificate.add_extension(extensions.create_extension(x[0], x[1], x[2]))
end
lib/minbox/version.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
module Minbox
- VERSION = '0.1.6'
+ VERSION = "0.1.6"
end
lib/minbox.rb
@@ -1,18 +1,18 @@
# frozen_string_literal: true
-require 'base64'
-require 'concurrent'
-require 'hashie'
-require 'listen'
-require 'logger'
-require 'socket'
+require "base64"
+require "concurrent"
+require "hashie"
+require "listen"
+require "logger"
+require "socket"
-require 'minbox/client'
-require 'minbox/core'
-require 'minbox/inbox'
-require 'minbox/publisher'
-require 'minbox/server'
-require 'minbox/version'
+require "minbox/client"
+require "minbox/core"
+require "minbox/inbox"
+require "minbox/publisher"
+require "minbox/server"
+require "minbox/version"
module Minbox
class Error < StandardError; end
spec/minbox/inbox_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
RSpec.describe Minbox::Inbox do
- subject! { described_class.instance(root_dir: 'tmp') }
+ subject! { described_class.instance(root_dir: "tmp") }
before do
IO.write("tmp/1.eml", Mail.new do
@@ -17,11 +17,11 @@ RSpec.describe Minbox::Inbox do
end
after do
- FileUtils.rm(Dir.glob('tmp/*.eml'))
+ FileUtils.rm(Dir.glob("tmp/*.eml"))
end
describe "#empty!" do
- before :example do
+ before do
IO.write("tmp/3.eml", Mail.new do
to Faker::Internet.email
from Faker::Internet.email
@@ -34,20 +34,20 @@ RSpec.describe Minbox::Inbox do
end
describe "#emails" do
- specify { expect(subject.emails(count: 2).map(&:subject)).to match_array(['[ACTION] goodbye world', 'hello world']) }
+ specify { expect(subject.emails(count: 2).map(&:subject)).to match_array(["[ACTION] goodbye world", "hello world"]) }
end
describe "#wait_until!" do
context "when the condition is satisfied" do
before { subject.wait_until! { |x| x.count == 2 } }
- specify { expect(subject.emails(count: 2).map(&:subject)).to match_array(['[ACTION] goodbye world', 'hello world']) }
+ specify { expect(subject.emails(count: 2).map(&:subject)).to match_array(["[ACTION] goodbye world", "hello world"]) }
end
context "when the condition is not satisfied" do
specify do
expect do
- subject.wait_until!(seconds: 0.1) { |inbox| false }
+ subject.wait_until!(seconds: 0.1) { |_inbox| false }
end.to raise_error(/timeout/)
end
end
@@ -55,10 +55,10 @@ RSpec.describe Minbox::Inbox do
describe "#open" do
context "when opening an email by subject" do
- specify { expect(subject.open(subject: '[ACTION] goodbye world').subject).to eql('[ACTION] goodbye world') }
- specify { expect(subject.open(subject: /goodbye/).subject).to eql('[ACTION] goodbye world') }
- specify { expect(subject.open(subject: /hello/).subject).to eql('hello world') }
- specify { expect(subject.open(subject: /world/).subject).to eql('hello world') }
+ specify { expect(subject.open(subject: "[ACTION] goodbye world").subject).to eql("[ACTION] goodbye world") }
+ specify { expect(subject.open(subject: /goodbye/).subject).to eql("[ACTION] goodbye world") }
+ specify { expect(subject.open(subject: /hello/).subject).to eql("hello world") }
+ specify { expect(subject.open(subject: /world/).subject).to eql("hello world") }
end
context "when opening an email not in the inbox" do
spec/minbox/server_spec.rb
@@ -1,22 +1,22 @@
# frozen_string_literal: true
RSpec.describe Minbox::Server do
- describe '#handle' do
- let(:host) { 'localhost' }
+ describe "#handle" do
+ let(:host) { "localhost" }
let(:port) { 8080 }
- context 'when handling a simple client' do
+ context "when handling a simple client" do
def create_mail(to: Faker::Internet.email, from: Faker::Internet.email)
Mail.new do |x|
x.from from
x.to to
- x.subject 'test message'
+ x.subject "test message"
x.body "#{Time.now} This is a test message."
yield x if block_given?
end
end
- context 'when sending a single email' do
+ context "when sending a single email" do
let(:result) do
Net::SMTP.start(host, port) do |smtp|
smtp.send_message(create_mail.to_s, Faker::Internet.email, Faker::Internet.email)
@@ -27,7 +27,7 @@ RSpec.describe Minbox::Server do
specify { expect(result.status.to_i).to be(250) }
end
- context 'when sending multiple emails' do
+ context "when sending multiple emails" do
let(:n) { rand(10) }
let(:result) do
Net::SMTP.start(host, port) do |smtp|
@@ -40,9 +40,9 @@ RSpec.describe Minbox::Server do
specify { expect(result).to eql(n) }
end
- context 'with plain authentication' do
+ context "with plain authentication" do
let(:result) do
- Net::SMTP.start(host, port, 'mail.from.domain', 'username', 'password', :plain) do |smtp|
+ Net::SMTP.start(host, port, "mail.from.domain", "username", "password", :plain) do |smtp|
smtp.send_message(create_mail.to_s, Faker::Internet.email, Faker::Internet.email)
end
end
@@ -51,9 +51,9 @@ RSpec.describe Minbox::Server do
specify { expect(result.status.to_i).to be(250) }
end
- context 'with login authentication' do
+ context "with login authentication" do
let(:result) do
- Net::SMTP.start(host, port, 'mail.from.domain', 'username', 'password', :login) do |smtp|
+ Net::SMTP.start(host, port, "mail.from.domain", "username", "password", :login) do |smtp|
smtp.send_message(create_mail.to_s, Faker::Internet.email, Faker::Internet.email)
end
end
@@ -62,12 +62,12 @@ RSpec.describe Minbox::Server do
specify { expect(result.status.to_i).to be(250) }
end
- context 'with attachment' do
+ context "with attachment" do
let(:result) do
mail = create_mail do |x|
x.add_file __FILE__
end
- Net::SMTP.start(host, port, 'mail.from.domain', 'username', 'password', :login) do |smtp|
+ Net::SMTP.start(host, port, "mail.from.domain", "username", "password", :login) do |smtp|
smtp.debug_output = STDOUT
smtp.send_message(mail.to_s, Faker::Internet.email, Faker::Internet.email)
end
@@ -77,14 +77,14 @@ RSpec.describe Minbox::Server do
specify { expect(result.status.to_i).to be(250) }
end
- context 'with html part' do
+ context "with html part" do
let(:result) do
mail = create_mail do |x|
x.text_part do
- body 'this is plain text'
+ body "this is plain text"
end
x.html_part do
- body '<h1>this is html</h1>'
+ body "<h1>this is html</h1>"
end
end
Net::SMTP.start(host, port) do |smtp|
@@ -96,20 +96,21 @@ RSpec.describe Minbox::Server do
specify { expect(result.status.to_i).to be(250) }
end
- context 'when upgrading to tls' do
+ context "when upgrading to tls" do
let(:result) do
`(echo 'QUIT'; sleep 1) | openssl s_client -connect #{host}:#{port} -starttls smtp 2>&1`
end
specify { expect(result).to end_with("DONE\n") }
- specify { expect(result).to include('250 OK') }
+ specify { expect(result).to include("250 OK") }
end
- context 'when sending multiple emails from multiple threads' do
+ context "when sending multiple emails from multiple threads" do
let!(:email) { Faker::Internet.email }
let!(:mail) { create_mail }
let!(:mail_string) { mail.to_s }
+ # rubocop:disable RSpec/ExampleLength
specify do
threads = []
10.times do |_n|
@@ -126,6 +127,7 @@ RSpec.describe Minbox::Server do
threads.map(&:join)
end
end
+ # rubocop:enable RSpec/ExampleLength
end
end
end
spec/spec_helper.rb
@@ -1,14 +1,14 @@
# frozen_string_literal: true
-require 'bundler/setup'
-require 'minbox'
-require 'net/smtp'
-require 'mail'
-require 'faker'
+require "bundler/setup"
+require "minbox"
+require "net/smtp"
+require "mail"
+require "faker"
RSpec.configure do |config|
# Enable flags like --only-failures and --next-failure
- config.example_status_persistence_file_path = '.rspec_status'
+ config.example_status_persistence_file_path = ".rspec_status"
# Disable RSpec exposing methods globally on `Module` and `main`
config.disable_monkey_patching!
.rubocop.yml
@@ -69,6 +69,9 @@ Metrics/LineLength:
IgnoredPatterns:
- '^#*'
+Naming/RescuedExceptionsVariableName:
+ PreferredName: error
+
Style/Documentation:
Enabled: false
@@ -76,7 +79,7 @@ Style/EachWithObject:
Enabled: false
Style/StringLiterals:
- EnforcedStyle: 'single_quotes'
+ EnforcedStyle: 'double_quotes'
Style/TrailingCommaInArrayLiteral:
Enabled: false
Gemfile
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-source 'https://rubygems.org'
+source "https://rubygems.org"
# Specify your gem's dependencies in minbox.gemspec
gemspec
minbox.gemspec
@@ -1,29 +1,29 @@
# frozen_string_literal: true
-lib = File.expand_path('lib', __dir__)
+lib = File.expand_path("lib", __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
-require 'minbox/version'
+require "minbox/version"
Gem::Specification.new do |spec|
- spec.name = 'minbox'
+ spec.name = "minbox"
spec.version = Minbox::VERSION
- spec.authors = ['mo khan']
- spec.email = ['mo@mokhan.ca']
+ spec.authors = ["mo khan"]
+ spec.email = ["mo@mokhan.ca"]
- spec.summary = 'A minimal smtp server.'
- spec.description = 'A minimal smtp server.'
- spec.homepage = 'https://www.mokhan.ca/'
- spec.license = 'MIT'
+ spec.summary = "A minimal smtp server."
+ spec.description = "A minimal smtp server."
+ spec.homepage = "https://www.mokhan.ca/"
+ spec.license = "MIT"
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
# to allow pushing to a single host or delete this section to allow pushing to any host.
if spec.respond_to?(:metadata)
- spec.metadata['homepage_uri'] = spec.homepage
- spec.metadata['source_code_uri'] = 'https://github.com/xlgmokha/minbox'
- spec.metadata['changelog_uri'] = 'https://github.com/xlgmokha/minbox/blob/CHANGELOG.md'
+ spec.metadata["homepage_uri"] = spec.homepage
+ spec.metadata["source_code_uri"] = "https://github.com/xlgmokha/minbox"
+ spec.metadata["changelog_uri"] = "https://github.com/xlgmokha/minbox/blob/CHANGELOG.md"
else
- raise 'RubyGems 2.0 or newer is required to protect against ' \
- 'public gem pushes.'
+ raise "RubyGems 2.0 or newer is required to protect against " \
+ "public gem pushes."
end
# Specify which files should be added to the gem when it is released.
@@ -31,21 +31,21 @@ Gem::Specification.new do |spec|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end
- spec.bindir = 'exe'
+ spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
- spec.require_paths = ['lib']
+ spec.require_paths = ["lib"]
- spec.add_dependency 'concurrent-ruby', '~> 1.1'
- spec.add_dependency 'hashie', '~> 3.6'
- spec.add_dependency 'listen', '~> 3.1'
- spec.add_dependency 'mail', '~> 2.7'
- spec.add_dependency 'redis', '~> 4.1'
- spec.add_dependency 'thor', '~> 0.20'
- spec.add_development_dependency 'bundler', '~> 2.0'
- spec.add_development_dependency 'bundler-audit', '~> 0.6'
- spec.add_development_dependency 'faker', '~> 1.9'
- spec.add_development_dependency 'rake', '~> 10.0'
- spec.add_development_dependency 'rspec', '~> 3.0'
- spec.add_development_dependency 'rubocop', '~> 0.52'
- spec.add_development_dependency 'rubocop-rspec', '~> 1.22'
+ spec.add_dependency "concurrent-ruby", "~> 1.1"
+ spec.add_dependency "hashie", "~> 3.6"
+ spec.add_dependency "listen", "~> 3.1"
+ spec.add_dependency "mail", "~> 2.7"
+ spec.add_dependency "redis", "~> 4.1"
+ spec.add_dependency "thor", "~> 0.20"
+ spec.add_development_dependency "bundler", "~> 2.0"
+ spec.add_development_dependency "bundler-audit", "~> 0.6"
+ spec.add_development_dependency "faker", "~> 1.9"
+ spec.add_development_dependency "rake", "~> 10.0"
+ spec.add_development_dependency "rspec", "~> 3.0"
+ spec.add_development_dependency "rubocop", "~> 0.52"
+ spec.add_development_dependency "rubocop-rspec", "~> 1.22"
end
Rakefile
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require 'bundler/audit/task'
-require 'bundler/gem_tasks'
-require 'rspec/core/rake_task'
-require 'rubocop/rake_task'
+require "bundler/audit/task"
+require "bundler/gem_tasks"
+require "rspec/core/rake_task"
+require "rubocop/rake_task"
RSpec::Core::RakeTask.new(:spec)
RuboCop::RakeTask.new(:rubocop)
Bundler::Audit::Task.new
-task lint: [:rubocop, 'bundle:audit']
+task lint: [:rubocop, "bundle:audit"]
task default: :spec