malm 0.0.4 → 0.0.5

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/Gemfile CHANGED
@@ -4,6 +4,11 @@ gem "json", "1.5.3"
4
4
  gem "clamp", "0.2.1"
5
5
  gem "sinatra", "1.2.6"
6
6
  gem "thin", "1.2.11"
7
+ gem "mail", "2.3.0"
8
+ gem "therubyracer", "0.9.2"
9
+ gem "execjs", "1.2.0"
10
+ gem "coffee-script", "2.2.0"
11
+ gem "daemons", "1.1.4"
7
12
 
8
13
  group :development do
9
14
  gem "rspec", "~> 2.3.0"
data/Gemfile.lock CHANGED
@@ -2,15 +2,30 @@ GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
4
  clamp (0.2.1)
5
+ coffee-script (2.2.0)
6
+ coffee-script-source
7
+ execjs
8
+ coffee-script-source (1.1.1)
5
9
  daemons (1.1.4)
6
10
  diff-lcs (1.1.2)
7
11
  eventmachine (0.12.10)
12
+ execjs (1.2.0)
13
+ multi_json (~> 1.0)
8
14
  git (1.2.5)
15
+ i18n (0.6.0)
9
16
  jeweler (1.6.4)
10
17
  bundler (~> 1.0)
11
18
  git (>= 1.2.5)
12
19
  rake
13
20
  json (1.5.3)
21
+ libv8 (3.3.10.2)
22
+ mail (2.3.0)
23
+ i18n (>= 0.4.0)
24
+ mime-types (~> 1.16)
25
+ treetop (~> 1.4.8)
26
+ mime-types (1.16)
27
+ multi_json (1.0.3)
28
+ polyglot (0.3.1)
14
29
  rack (1.3.1)
15
30
  rake (0.9.2)
16
31
  rcov (0.9.9)
@@ -25,11 +40,15 @@ GEM
25
40
  sinatra (1.2.6)
26
41
  rack (~> 1.1)
27
42
  tilt (< 2.0, >= 1.2.2)
43
+ therubyracer (0.9.2)
44
+ libv8 (~> 3.3.10)
28
45
  thin (1.2.11)
29
46
  daemons (>= 1.0.9)
30
47
  eventmachine (>= 0.12.6)
31
48
  rack (>= 1.0.0)
32
49
  tilt (1.3.2)
50
+ treetop (1.4.9)
51
+ polyglot (>= 0.3.1)
33
52
 
34
53
  PLATFORMS
35
54
  ruby
@@ -37,9 +56,14 @@ PLATFORMS
37
56
  DEPENDENCIES
38
57
  bundler (~> 1.0.0)
39
58
  clamp (= 0.2.1)
59
+ coffee-script (= 2.2.0)
60
+ daemons (= 1.1.4)
61
+ execjs (= 1.2.0)
40
62
  jeweler (~> 1.6.2)
41
63
  json (= 1.5.3)
64
+ mail (= 2.3.0)
42
65
  rcov
43
66
  rspec (~> 2.3.0)
44
67
  sinatra (= 1.2.6)
68
+ therubyracer (= 0.9.2)
45
69
  thin (= 1.2.11)
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ Malm
2
+ ====
3
+ Easy SMTP server for local development
4
+ --------------------------------------
5
+ Malm is a super simple SMTP trap that will catch everything sent to it, not forward it on, but instead display on a local web server.
6
+
7
+ This is useful for when you're doing local development that requires your app to send mail, but you don't want to go through all the hassle of setting up a full featured mail server (and then pointing your mail client at it).
8
+
9
+ Install
10
+ -------
11
+ gem install malm
12
+
13
+ Basic Usage
14
+ -----------
15
+ The following command starts Malm up. Running on it's defaults of listening for SMTP on port 2525, client web app running on port 4567, and starting as a process in the foreground
16
+
17
+ malm
18
+
19
+ If you want to start up on regular SMTP port 25, you'll probably need to run it as sudo
20
+
21
+ sudo malm -p 25
22
+
23
+ For more info try
24
+
25
+ sudo malm --help
26
+
27
+ Viewing Messages
28
+ ----------------
29
+ Once it's up and running, it works just like any other SMTP server - your app sends it mail, then happily goes about it's business. To view the mail messages Malm has picked up, point your web browser at [http://localhost:4567](http://localhost:4567)
30
+
31
+ Who the hell?
32
+ -------------
33
+ I blame [@madlep](https://twitter.com/#!/madlep)
34
+
35
+ Contributing
36
+ ------------
37
+ Fork + pull request. Nice and easy.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.4
1
+ 0.0.5
data/bin/malm CHANGED
@@ -1,52 +1,48 @@
1
1
  #!/usr/bin/env ruby
2
2
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
3
- require "malm_smtp_server"
4
- require "malm_web"
5
- require "clamp"
6
- require 'message_db'
3
+ require 'malm'
4
+ require 'clamp'
5
+ require 'daemons'
7
6
 
8
7
  class MalmCommand < Clamp::Command
8
+ option "--start", :flag, "Start malm as a daemon process. PID will be written to '<PID DIRECTORY>/malm-<SMTP PORT>-<WEB PORT>.pid'"
9
+ option "--stop", :flag, "Stop malm running as a daemon process. Process id '<PID DIRECTORY>/malm-<SMTP PORT>-<WEB PORT>.pid' will be stopped"
10
+ option "--restart", :flag, "Restart malm running as a daemon process"
11
+ option "--piddir", "PID DIRECTORY", "Where to put pids", :default => "/var/run"
9
12
  option ["-l", "--log"], "FILE", "file to log mail messages to (optional)"
10
-
11
- option ["-p", "--stmpport"], "STMP PORT", "SMTP port to listen on", :default => 2525 do |port|
13
+ option ["-p", "--smtpport"], "SMTP PORT", "SMTP port to listen on", :default => 2525 do |port|
12
14
  Integer(port)
13
15
  end
14
-
15
16
  option ["-w", "--webport"], "WEB PORT", "Port for client web app to view malmed messages", :default => 4567 do |port|
16
17
  Integer(port)
17
18
  end
18
19
 
19
- def execute
20
- db = create_db
21
- smtp = run_smtp!(db)
22
- web = run_web!(db)
23
- end
24
-
25
- private
26
- def create_db
27
- MessageDb.new
28
- end
29
-
30
- def run_smtp!(db)
31
- smtp_server = MalmSMTPServer.new(stmpport)
32
- smtp_server.mail_log = log
33
- smtp_server.message_db = db
20
+ def execute
21
+ malm_proc = proc{
22
+ malm = Malm.new(:log => log, :smtpport => smtpport, :webport => webport)
23
+ malm.run!
24
+ }
34
25
 
35
- begin
36
- smtp_server.start
37
- rescue Errno::EACCES
38
- STDERR.puts("Don't have permission to start SMTP server on port #{stmpport}. Maybe run with sudo?")
39
- exit 1
26
+ if start? || stop? || restart?
27
+ mode = nil
28
+ mode ||= start? ? "start" : nil
29
+ mode ||= stop? ? "stop" : nil
30
+ mode ||= restart? ? "restart" : nil
31
+
32
+ daemon_options = {
33
+ :ARGV => [mode],
34
+ :dir_mode => :normal,
35
+ :dir => piddir,
36
+ :multiple => true,
37
+ :monitor => true,
38
+ :app_name => "malm-#{smtpport}-#{webport}"
39
+ }
40
+
41
+ Daemons.run_proc("malm-#{smtpport}-#{webport}", daemon_options, &malm_proc)
42
+ else
43
+ malm_proc.call
40
44
  end
41
- smtp_server
42
- end
43
-
44
- def run_web!(db)
45
- MalmWeb.set :port, webport
46
- MalmWeb.set :message_db, db
47
- MalmWeb.run!
48
45
  end
49
46
  end
50
-
51
47
  MalmCommand.run
52
48
 
@@ -0,0 +1,33 @@
1
+ require 'thread'
2
+
3
+ class Malm
4
+ class MessageDb
5
+
6
+ def initialize
7
+ @sempahore = Mutex.new
8
+
9
+ @message_db = []
10
+ end
11
+
12
+ def create(message)
13
+ @sempahore.synchronize do
14
+ message[:id] = @message_db.size
15
+ @message_db << message
16
+ @message_db.size
17
+ end
18
+ end
19
+
20
+ def find_all
21
+ @sempahore.synchronize do
22
+ @message_db.dup
23
+ end
24
+ end
25
+
26
+ def find(id)
27
+ @sempahore.synchronize do
28
+ @message_db[id].dup
29
+ end
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,95 @@
1
+ # based on copy + paste from http://snippets.dzone.com/posts/show/5152
2
+ require 'gserver'
3
+
4
+ class Malm
5
+ class SMTPServer < GServer
6
+
7
+ attr_accessor :mail_log
8
+ attr_accessor :message_db
9
+
10
+ class Session
11
+ attr_accessor :data_mode, :data_mode, :email_body, :mail_from, :rcpt_to, :subject
12
+ alias :data_mode? :data_mode
13
+
14
+ def initialize
15
+ @data_mode = false
16
+ @email_body = ""
17
+ end
18
+
19
+ end
20
+
21
+ def serve(io)
22
+ session = Session.new
23
+ puts "Connected"
24
+ io.print "220 hello\r\n"
25
+ loop do
26
+ data = io.gets
27
+ puts ">>" + data
28
+ ok, op = process_line(data, session)
29
+ puts "<<" + op
30
+ io.print op
31
+ break unless ok
32
+ break if io.closed?
33
+ end
34
+ begin
35
+ io.close
36
+ db_insert(session)
37
+ rescue => e
38
+ log "something screwed up..."
39
+ log e.backtrace
40
+ end
41
+ end
42
+
43
+ def process_line(line, session)
44
+ if (session.data_mode?) && (line.chomp =~ /^\.$/)
45
+ session.data_mode = false
46
+ return true, "250 OK\r\n"
47
+ elsif session.data_mode?
48
+ session.email_body += line
49
+ return true, ""
50
+ elsif (line =~ /^(HELO|EHLO)/)
51
+ return true, "250 and..?\r\n"
52
+ elsif (line =~ /^QUIT/)
53
+ return false, "221 bye\r\n"
54
+ elsif (line =~ /^MAIL FROM\:/)
55
+ session.mail_from = (/^MAIL FROM\:<(.+)>.*$/).match(line)[1]
56
+ return true, "250 OK\r\n"
57
+ elsif (line =~ /^RCPT TO\:/)
58
+ session.rcpt_to = (/^RCPT TO\:<(.+)>.*$/).match(line)[1]
59
+ return true, "250 OK\r\n"
60
+ elsif (line =~ /^DATA/)
61
+ session.data_mode = true
62
+ return true, "354 Enter message, ending with \".\" on a line by itself\r\n"
63
+ else
64
+ return true, "500 ERROR\r\n"
65
+ end
66
+ end
67
+
68
+ def db_insert(session)
69
+ subject_regex = /^Subject\: (.+)$/
70
+
71
+ subject_match = subject_regex.match(session.email_body)
72
+ subject = subject_match ? subject_match[1] : ""
73
+ subject.strip!
74
+
75
+ message = {:subject => subject, :from => session.mail_from, :to => session.rcpt_to, :body => session.email_body}
76
+
77
+ @mail_log_fd.puts(message.inspect) if @mail_log_fd
78
+ @message_db.create(message) if @message_db
79
+
80
+ log("Message received: #{message.inspect}")
81
+ end
82
+
83
+ protected
84
+ def starting
85
+ @mail_log_fd = File.open(@mail_log, "a") if @mail_log
86
+ super
87
+ end
88
+
89
+ def stopping
90
+ @mail_log_fd.close if @mail_log_fd
91
+ super
92
+ end
93
+
94
+ end
95
+ end
data/lib/malm/web.rb ADDED
@@ -0,0 +1,63 @@
1
+ require 'thin'
2
+ require 'sinatra/base'
3
+ require 'json'
4
+ require 'v8'
5
+ require 'execjs'
6
+ require 'coffee_script'
7
+ require 'mail'
8
+
9
+ class Malm
10
+ class Web < Sinatra::Base
11
+
12
+ set :public, File.join(File.dirname(__FILE__), "..", "..", "web", "static")
13
+ set :views, File.join(File.dirname(__FILE__), "..", "..", "web", "views")
14
+
15
+ get "/messages.json" do
16
+ content_type :json
17
+ settings.message_db.find_all.map{|m|
18
+ m = m.dup
19
+ m.delete(:body)
20
+ m[:body_urls] = {:html => url("/messages/#{m[:id]}/body.html"), :text => url("/messages/#{m[:id]}/body.text")}
21
+ m
22
+ }.to_json
23
+ end
24
+
25
+ get "/messages/:id.json" do
26
+ content_type :json
27
+ find_message(params[:id]).to_json
28
+ end
29
+
30
+ get "/messages/:id/body.:type" do
31
+ render_message(params[:id], params[:type])
32
+ end
33
+
34
+ get "/coffee/:script.coffee" do
35
+ content_type "text/javascript"
36
+ coffee params[:script].to_sym
37
+ end
38
+
39
+ private
40
+ def find_message(id)
41
+ id = Integer(id)
42
+ settings.message_db.find(id)
43
+ end
44
+
45
+ def render_message(id, type)
46
+ content_type type
47
+
48
+ supported = ["text", "html"]
49
+ unless supported.include?(type.to_s)
50
+ halt 415, "don't know how to display message #{id} as #{type}. Try one of #{supported.join(",")}"
51
+ return
52
+ end
53
+
54
+ msg_obj = find_message(id)
55
+ if msg_obj
56
+ Mail::Message.new(msg_obj[:body]).send("#{type}_part".to_sym).body.to_s
57
+ else
58
+ status 404
59
+ "don't know about message #{id}"
60
+ end
61
+ end
62
+ end
63
+ end
data/lib/malm.rb ADDED
@@ -0,0 +1,42 @@
1
+ require 'malm/message_db'
2
+ require 'malm/smtp_server'
3
+ require 'malm/web'
4
+
5
+ class Malm
6
+ attr_accessor :log, :smtpport, :webport
7
+
8
+ def initialize(options={})
9
+ options.each{|k,v| send("#{k}=", v)}
10
+ end
11
+
12
+ def run!
13
+ create_db
14
+ run_smtp!
15
+ run_web!
16
+ end
17
+
18
+ private
19
+ def create_db
20
+ @db = Malm::MessageDb.new
21
+ end
22
+
23
+ def run_smtp!
24
+ smtp_server = Malm::SMTPServer.new(@smtpport)
25
+ smtp_server.mail_log = @log
26
+ smtp_server.message_db = @db
27
+
28
+ begin
29
+ smtp_server.start
30
+ rescue Errno::EACCES
31
+ STDERR.puts("Don't have permission to start SMTP server on port #{smtpport}. Maybe run with sudo?")
32
+ exit 1
33
+ end
34
+ smtp_server
35
+ end
36
+
37
+ def run_web!
38
+ Malm::Web.set :port, @webport
39
+ Malm::Web.set :message_db, @db
40
+ Malm::Web.run!
41
+ end
42
+ end
data/malm.gemspec CHANGED
@@ -5,17 +5,18 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{malm}
8
- s.version = "0.0.4"
8
+ s.version = "0.0.5"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["madlep"]
12
- s.date = %q{2011-07-14}
12
+ s.date = %q{2011-07-23}
13
13
  s.default_executable = %q{malm}
14
14
  s.description = %q{SMTP server with web interface for easy local development. Sets up a little mail server that you can send messages to, and provides a web front end to let you see what your app did.}
15
15
  s.email = %q{julian.doherty.ml@gmail.com}
16
16
  s.executables = ["malm"]
17
17
  s.extra_rdoc_files = [
18
- "LICENSE.txt"
18
+ "LICENSE.txt",
19
+ "README.md"
19
20
  ]
20
21
  s.files = [
21
22
  ".document",
@@ -23,21 +24,31 @@ Gem::Specification.new do |s|
23
24
  "Gemfile",
24
25
  "Gemfile.lock",
25
26
  "LICENSE.txt",
27
+ "README.md",
26
28
  "Rakefile",
27
29
  "VERSION",
28
30
  "bin/malm",
29
- "lib/malm_smtp_server.rb",
30
- "lib/malm_web.rb",
31
- "lib/message_db.rb",
31
+ "lib/malm.rb",
32
+ "lib/malm/message_db.rb",
33
+ "lib/malm/smtp_server.rb",
34
+ "lib/malm/web.rb",
32
35
  "malm.gemspec",
33
36
  "spec/bin/malm_test_message",
34
37
  "spec/help_spec.rb",
35
- "spec/spec_helper.rb"
38
+ "spec/spec_helper.rb",
39
+ "web/static/css/style.css",
40
+ "web/static/favicon.ico",
41
+ "web/static/index.html",
42
+ "web/static/js/backbone-min.js",
43
+ "web/static/js/backbone.js",
44
+ "web/static/js/jquery-1.6.2.js",
45
+ "web/static/js/jquery-1.6.2.min.js",
46
+ "web/views/hello_world.coffee"
36
47
  ]
37
48
  s.homepage = %q{http://github.com/madlep/malm}
38
49
  s.licenses = ["MIT"]
39
50
  s.require_paths = ["lib"]
40
- s.rubygems_version = %q{1.4.2}
51
+ s.rubygems_version = %q{1.6.2}
41
52
  s.summary = %q{Easy SMTP server for local development}
42
53
 
43
54
  if s.respond_to? :specification_version then
@@ -48,6 +59,11 @@ Gem::Specification.new do |s|
48
59
  s.add_runtime_dependency(%q<clamp>, ["= 0.2.1"])
49
60
  s.add_runtime_dependency(%q<sinatra>, ["= 1.2.6"])
50
61
  s.add_runtime_dependency(%q<thin>, ["= 1.2.11"])
62
+ s.add_runtime_dependency(%q<mail>, ["= 2.3.0"])
63
+ s.add_runtime_dependency(%q<therubyracer>, ["= 0.9.2"])
64
+ s.add_runtime_dependency(%q<execjs>, ["= 1.2.0"])
65
+ s.add_runtime_dependency(%q<coffee-script>, ["= 2.2.0"])
66
+ s.add_runtime_dependency(%q<daemons>, ["= 1.1.4"])
51
67
  s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
52
68
  s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
53
69
  s.add_development_dependency(%q<jeweler>, ["~> 1.6.2"])
@@ -57,6 +73,11 @@ Gem::Specification.new do |s|
57
73
  s.add_dependency(%q<clamp>, ["= 0.2.1"])
58
74
  s.add_dependency(%q<sinatra>, ["= 1.2.6"])
59
75
  s.add_dependency(%q<thin>, ["= 1.2.11"])
76
+ s.add_dependency(%q<mail>, ["= 2.3.0"])
77
+ s.add_dependency(%q<therubyracer>, ["= 0.9.2"])
78
+ s.add_dependency(%q<execjs>, ["= 1.2.0"])
79
+ s.add_dependency(%q<coffee-script>, ["= 2.2.0"])
80
+ s.add_dependency(%q<daemons>, ["= 1.1.4"])
60
81
  s.add_dependency(%q<rspec>, ["~> 2.3.0"])
61
82
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
62
83
  s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
@@ -67,6 +88,11 @@ Gem::Specification.new do |s|
67
88
  s.add_dependency(%q<clamp>, ["= 0.2.1"])
68
89
  s.add_dependency(%q<sinatra>, ["= 1.2.6"])
69
90
  s.add_dependency(%q<thin>, ["= 1.2.11"])
91
+ s.add_dependency(%q<mail>, ["= 2.3.0"])
92
+ s.add_dependency(%q<therubyracer>, ["= 0.9.2"])
93
+ s.add_dependency(%q<execjs>, ["= 1.2.0"])
94
+ s.add_dependency(%q<coffee-script>, ["= 2.2.0"])
95
+ s.add_dependency(%q<daemons>, ["= 1.1.4"])
70
96
  s.add_dependency(%q<rspec>, ["~> 2.3.0"])
71
97
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
72
98
  s.add_dependency(%q<jeweler>, ["~> 1.6.2"])