inkcite 1.0.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.
- checksums.yaml +7 -0
- data/LICENSE +20 -0
- data/README.md +110 -0
- data/Rakefile +8 -0
- data/assets/facebook-like.css +62 -0
- data/assets/facebook-like.js +59 -0
- data/assets/init/config.yml +97 -0
- data/assets/init/helpers.tsv +31 -0
- data/assets/init/source.html +60 -0
- data/assets/init/source.txt +6 -0
- data/bin/inkcite +6 -0
- data/inkcite.gemspec +42 -0
- data/lib/inkcite.rb +32 -0
- data/lib/inkcite/cli/base.rb +128 -0
- data/lib/inkcite/cli/build.rb +130 -0
- data/lib/inkcite/cli/init.rb +58 -0
- data/lib/inkcite/cli/preview.rb +30 -0
- data/lib/inkcite/cli/server.rb +123 -0
- data/lib/inkcite/cli/test.rb +61 -0
- data/lib/inkcite/email.rb +219 -0
- data/lib/inkcite/mailer.rb +140 -0
- data/lib/inkcite/minifier.rb +151 -0
- data/lib/inkcite/parser.rb +111 -0
- data/lib/inkcite/renderer.rb +177 -0
- data/lib/inkcite/renderer/base.rb +186 -0
- data/lib/inkcite/renderer/button.rb +168 -0
- data/lib/inkcite/renderer/div.rb +29 -0
- data/lib/inkcite/renderer/element.rb +82 -0
- data/lib/inkcite/renderer/footnote.rb +132 -0
- data/lib/inkcite/renderer/google_analytics.rb +35 -0
- data/lib/inkcite/renderer/image.rb +95 -0
- data/lib/inkcite/renderer/image_base.rb +82 -0
- data/lib/inkcite/renderer/in_browser.rb +38 -0
- data/lib/inkcite/renderer/like.rb +73 -0
- data/lib/inkcite/renderer/link.rb +243 -0
- data/lib/inkcite/renderer/litmus.rb +33 -0
- data/lib/inkcite/renderer/lorem.rb +39 -0
- data/lib/inkcite/renderer/mobile_image.rb +67 -0
- data/lib/inkcite/renderer/mobile_style.rb +40 -0
- data/lib/inkcite/renderer/mobile_toggle.rb +27 -0
- data/lib/inkcite/renderer/outlook_background.rb +48 -0
- data/lib/inkcite/renderer/partial.rb +31 -0
- data/lib/inkcite/renderer/preheader.rb +22 -0
- data/lib/inkcite/renderer/property.rb +39 -0
- data/lib/inkcite/renderer/responsive.rb +334 -0
- data/lib/inkcite/renderer/span.rb +21 -0
- data/lib/inkcite/renderer/table.rb +67 -0
- data/lib/inkcite/renderer/table_base.rb +149 -0
- data/lib/inkcite/renderer/td.rb +92 -0
- data/lib/inkcite/uploader.rb +173 -0
- data/lib/inkcite/util.rb +85 -0
- data/lib/inkcite/version.rb +3 -0
- data/lib/inkcite/view.rb +745 -0
- data/lib/inkcite/view/context.rb +38 -0
- data/lib/inkcite/view/media_query.rb +60 -0
- data/lib/inkcite/view/tag_stack.rb +38 -0
- data/test/email_spec.rb +16 -0
- data/test/parser_spec.rb +72 -0
- data/test/project/config.yml +98 -0
- data/test/project/helpers.tsv +56 -0
- data/test/project/images/inkcite.jpg +0 -0
- data/test/project/source.html +58 -0
- data/test/project/source.txt +6 -0
- data/test/renderer/button_spec.rb +45 -0
- data/test/renderer/div_spec.rb +101 -0
- data/test/renderer/element_spec.rb +31 -0
- data/test/renderer/footnote_spec.rb +57 -0
- data/test/renderer/image_spec.rb +82 -0
- data/test/renderer/link_spec.rb +84 -0
- data/test/renderer/mobile_image_spec.rb +27 -0
- data/test/renderer/mobile_style_spec.rb +37 -0
- data/test/renderer/td_spec.rb +126 -0
- data/test/renderer_spec.rb +28 -0
- data/test/view_spec.rb +15 -0
- metadata +333 -0
data/bin/inkcite
ADDED
data/inkcite.gemspec
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'inkcite/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
|
8
|
+
spec.name = "inkcite"
|
9
|
+
spec.version = Inkcite::VERSION
|
10
|
+
spec.platform = Gem::Platform::RUBY
|
11
|
+
spec.authors = ["Jeffrey D. Hoffman"]
|
12
|
+
spec.email = ["inkcite@inkceptional.com"]
|
13
|
+
spec.description = "An opinionated modern, responsive HTML email generator with integrated helpers, versioning, live previews, minification and testing."
|
14
|
+
spec.summary = "Simplifying email development"
|
15
|
+
spec.homepage = "https://github.com/inkceptional/inkcite"
|
16
|
+
spec.license = "MIT"
|
17
|
+
|
18
|
+
spec.files = `git ls-files`.split($/) - [".gitignore", "Gemfile", "Gemfile.lock"]
|
19
|
+
spec.executables << 'inkcite'
|
20
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
# Sorry, not yet
|
24
|
+
spec.has_rdoc = false
|
25
|
+
|
26
|
+
spec.add_dependency 'activesupport'
|
27
|
+
spec.add_dependency 'builder'
|
28
|
+
spec.add_dependency 'erubis'
|
29
|
+
spec.add_dependency 'faker'
|
30
|
+
spec.add_dependency 'litmus'
|
31
|
+
spec.add_dependency 'mail'
|
32
|
+
spec.add_dependency 'net-sftp'
|
33
|
+
spec.add_dependency 'rack'
|
34
|
+
spec.add_dependency 'rubyzip'
|
35
|
+
spec.add_dependency 'thor'
|
36
|
+
spec.add_dependency 'yui-compressor'
|
37
|
+
|
38
|
+
spec.add_development_dependency "bundler", "~> 1.1"
|
39
|
+
spec.add_development_dependency "rake"
|
40
|
+
spec.add_development_dependency "minitest"
|
41
|
+
|
42
|
+
end
|
data/lib/inkcite.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'erubis'
|
2
|
+
require 'i18n'
|
3
|
+
require 'set'
|
4
|
+
require 'uri'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
require 'active_support/core_ext/hash/keys.rb' # Symbolize keys!
|
8
|
+
require 'active_support/core_ext/module/delegation.rb'
|
9
|
+
require 'active_support/core_ext/object/blank'
|
10
|
+
require 'active_support/core_ext/object/to_query'
|
11
|
+
require 'active_support/core_ext/string/inflections'
|
12
|
+
require 'active_support/core_ext/string/starts_ends_with'
|
13
|
+
|
14
|
+
require 'inkcite/version'
|
15
|
+
require 'inkcite/email'
|
16
|
+
require 'inkcite/util'
|
17
|
+
require 'inkcite/view'
|
18
|
+
require 'inkcite/minifier'
|
19
|
+
require 'inkcite/parser'
|
20
|
+
require 'inkcite/renderer'
|
21
|
+
|
22
|
+
module Inkcite
|
23
|
+
|
24
|
+
def self.asset_path
|
25
|
+
File.expand_path('../../..', File.dirname(__FILE__))
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
# Make sure only available locales are used. This will be the default in the
|
31
|
+
# future but we need this to silence a deprecation warning from 0.6.9
|
32
|
+
I18n.config.enforce_available_locales = true
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Inkcite
|
5
|
+
module Cli
|
6
|
+
class Base < Thor
|
7
|
+
|
8
|
+
desc 'build [options]', 'Build the static email assets for deployment'
|
9
|
+
option :archive,
|
10
|
+
:aliases => '-a',
|
11
|
+
:desc => 'The name of the archive to compress final assets into'
|
12
|
+
option :force,
|
13
|
+
:aliases => '-f',
|
14
|
+
:desc => 'Build even if there are errors (not recommended)',
|
15
|
+
:type => :boolean
|
16
|
+
|
17
|
+
def build
|
18
|
+
require_relative 'build'
|
19
|
+
Cli::Build.invoke(email, {
|
20
|
+
:archive => options['archive'],
|
21
|
+
:force => options['force']
|
22
|
+
})
|
23
|
+
end
|
24
|
+
|
25
|
+
desc 'init NAME [options]', 'Initialize a new email project in the NAME directory'
|
26
|
+
option :from,
|
27
|
+
:aliases => '-f',
|
28
|
+
:desc => 'Clones an existing Inkcite project into a new one'
|
29
|
+
|
30
|
+
def init name
|
31
|
+
require_relative 'init'
|
32
|
+
Cli::Init.invoke(name, options)
|
33
|
+
end
|
34
|
+
|
35
|
+
desc 'preview TO [options]', 'Send a preview of the email recipient list: developer, internal or client'
|
36
|
+
|
37
|
+
def preview to=:developer
|
38
|
+
require_relative 'preview'
|
39
|
+
Cli::Preview.invoke(email, to, options)
|
40
|
+
end
|
41
|
+
|
42
|
+
desc 'server [options]', 'Start the preview server'
|
43
|
+
option :environment,
|
44
|
+
:aliases => '-e',
|
45
|
+
:default => 'development',
|
46
|
+
:desc => 'The environment Inkcite will run under'
|
47
|
+
option :format,
|
48
|
+
:aliases => '-f',
|
49
|
+
:default => 'email',
|
50
|
+
:desc => 'The format Inkcite will display - either email, browser or text'
|
51
|
+
method_option :host,
|
52
|
+
:type => :string,
|
53
|
+
:aliases => '-h',
|
54
|
+
:default => '0.0.0.0',
|
55
|
+
:desc => 'The ip address Inkcite will bind to'
|
56
|
+
method_option :port,
|
57
|
+
:aliases => '-p',
|
58
|
+
:default => '4567',
|
59
|
+
:desc => 'The port Inkcite will listen on',
|
60
|
+
:type => :numeric
|
61
|
+
option :version,
|
62
|
+
:aliases => '-v',
|
63
|
+
:desc => 'Render a specific version of the email'
|
64
|
+
|
65
|
+
def server
|
66
|
+
require_relative 'server'
|
67
|
+
|
68
|
+
Cli::Server.start(email, {
|
69
|
+
:environment => environment,
|
70
|
+
:format => format,
|
71
|
+
:host => options['host'],
|
72
|
+
:port => options['port'],
|
73
|
+
:version => version
|
74
|
+
})
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
desc 'test [options]', 'Tests (or re-tests) the email with Litmus'
|
79
|
+
option :new,
|
80
|
+
:aliases => '-n',
|
81
|
+
:desc => 'Forces a new test to be created, otherwise will revision an existing test if present',
|
82
|
+
:type => :boolean
|
83
|
+
|
84
|
+
def test
|
85
|
+
require_relative 'test'
|
86
|
+
Cli::Test.invoke(email, options)
|
87
|
+
end
|
88
|
+
|
89
|
+
desc 'upload', 'Upload the preview version to your CDN or remote image server'
|
90
|
+
option :force,
|
91
|
+
:aliases => '-f',
|
92
|
+
:desc => "Forces files to be uploaded regardless of whether or not they've changed",
|
93
|
+
:type => :boolean
|
94
|
+
def upload
|
95
|
+
options[:force] ? email.upload! : email.upload
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
# Resolves the desired environment (e.g. :development or :preview)
|
101
|
+
# from Thor's commandline options.
|
102
|
+
def environment
|
103
|
+
options['environment'] || :development
|
104
|
+
end
|
105
|
+
|
106
|
+
def email
|
107
|
+
Email.new(Dir.pwd)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Resolves the desired format (e.g. :browser or :email) from Thor's
|
111
|
+
# commandline options.
|
112
|
+
def format
|
113
|
+
options['format'] || :email
|
114
|
+
end
|
115
|
+
|
116
|
+
# Resolves the desired version (typically blank or :default) from
|
117
|
+
# Thor's commandline options.
|
118
|
+
def version
|
119
|
+
options['version']
|
120
|
+
end
|
121
|
+
|
122
|
+
def view
|
123
|
+
email.view(environment, format, version)
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module Inkcite
|
2
|
+
module Cli
|
3
|
+
class Build
|
4
|
+
|
5
|
+
def self.invoke email, opts
|
6
|
+
|
7
|
+
errors = false
|
8
|
+
|
9
|
+
# Don't allow production files to be built if there are errors.
|
10
|
+
email.views(:production) do |ev|
|
11
|
+
|
12
|
+
ev.render!
|
13
|
+
|
14
|
+
if !ev.errors.blank?
|
15
|
+
puts "The #{ev.version} version (#{ev.format}) has #{ev.errors.size} errors:"
|
16
|
+
puts " - #{ev.errors.join("\n - ")}"
|
17
|
+
errors = true
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
abort("Fix errors or use --force to build") if errors && !opts[:force]
|
23
|
+
|
24
|
+
# First, compile all assets to the build directory.
|
25
|
+
build_to_dir email, opts
|
26
|
+
|
27
|
+
# No archive? Build to files instead.
|
28
|
+
archive = opts[:archive]
|
29
|
+
build_archive(email, opts) unless archive.blank?
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Configuration value controlling where the production files will
|
36
|
+
# be created
|
37
|
+
BUILD_PATH = :'build-path'
|
38
|
+
|
39
|
+
def self.build_archive email, opts
|
40
|
+
|
41
|
+
require 'zip'
|
42
|
+
|
43
|
+
# This is the fully-qualified path to the .zip file.
|
44
|
+
zip_file = File.expand_path(opts[:archive])
|
45
|
+
puts "Archiving to #{zip_file} ..."
|
46
|
+
|
47
|
+
# The Zip::File will try to update an existing archive so just blow the old
|
48
|
+
# one away if it still exists.
|
49
|
+
File.delete(zip_file) if File.exists?(zip_file)
|
50
|
+
|
51
|
+
# The absolute path to the build directories
|
52
|
+
build_html_to = build_path(email)
|
53
|
+
build_images_to = build_images_path(email)
|
54
|
+
|
55
|
+
Zip::File.open(zip_file, Zip::File::CREATE) do |zip|
|
56
|
+
|
57
|
+
# Add the minified images to the .zip archive
|
58
|
+
if File.exists?(build_images_to)
|
59
|
+
Dir.foreach(build_images_to) do |img|
|
60
|
+
img_path = File.join(build_images_to, img)
|
61
|
+
zip.add(File.join(Inkcite::Email::IMAGES, img), img_path) unless File.directory?(img_path)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
Dir.foreach(build_html_to) do |file|
|
66
|
+
file_path = File.join(build_html_to, file)
|
67
|
+
zip.add(file, file_path)
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
# Remove the build directory
|
73
|
+
FileUtils.rm_rf(build_html_to)
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.build_to_dir email, opts
|
78
|
+
|
79
|
+
# The absolute path to the build directories
|
80
|
+
build_html_to = build_path(email)
|
81
|
+
build_images_to = build_images_path(email)
|
82
|
+
|
83
|
+
puts "Building to #{build_html_to}"
|
84
|
+
|
85
|
+
# Sanity check to ensure we're not building to the same
|
86
|
+
# directory as we're working.
|
87
|
+
if File.identical?(email.path, build_html_to)
|
88
|
+
puts "Working path and build path can not be the same. Change the '#{BUILD_PATH}' value in your config.yml."
|
89
|
+
exit(1)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Clear the existing build-to directory so we don't get any
|
93
|
+
# lingering files from the last build.
|
94
|
+
FileUtils.rm_rf build_html_to
|
95
|
+
|
96
|
+
# Remove any existing images directory and then create a new one to
|
97
|
+
# ensure the entire build path exists.
|
98
|
+
FileUtils.mkpath build_images_to
|
99
|
+
|
100
|
+
# Check to see if images should be optimized and if so, perform said
|
101
|
+
# optimization on new or updated images.
|
102
|
+
email.optimize_images if email.optimize_images?
|
103
|
+
|
104
|
+
# For each of the production views, build the HTML and links files.
|
105
|
+
email.views(:production) do |ev|
|
106
|
+
|
107
|
+
File.open(File.join(build_html_to, ev.file_name), 'w') { |f| ev.write(f) }
|
108
|
+
|
109
|
+
# Tracked link CSV
|
110
|
+
File.open(File.join(build_html_to, ev.links_file_name), 'w') { |f| ev.write_links_csv(f) } if ev.track_links?
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
# Copy all of the source images into the build directory in preparation
|
115
|
+
# for optimization
|
116
|
+
FileUtils.cp_r(File.join(email.optimized_image_dir, '.'), build_images_to)
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.build_path email
|
121
|
+
File.expand_path email.config[BUILD_PATH] || 'build'
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.build_images_path email
|
125
|
+
File.join(build_path(email), Email::IMAGES)
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Inkcite
|
2
|
+
module Cli
|
3
|
+
class Init
|
4
|
+
|
5
|
+
def self.invoke path, opts
|
6
|
+
|
7
|
+
full_init_path = File.expand_path(path)
|
8
|
+
|
9
|
+
# Sanity check to make sure we're not writing over an existing
|
10
|
+
# Inkcite project.
|
11
|
+
abort "It appears that an Inkcite already exists in #{path}" if File.exists?(File.join(full_init_path, 'config.yml'))
|
12
|
+
|
13
|
+
init_image_path = File.join(path, Inkcite::Email::IMAGES)
|
14
|
+
full_init_image_path = File.join(full_init_path, Inkcite::Email::IMAGES)
|
15
|
+
|
16
|
+
# Create the images directory first because it's the deepest level
|
17
|
+
# of the project structure.
|
18
|
+
FileUtils.mkpath(full_init_image_path)
|
19
|
+
|
20
|
+
puts "Created #{init_image_path}"
|
21
|
+
|
22
|
+
# Check to see if the user specified a --from path that is used to
|
23
|
+
# clone an existing project rather than init a new one.
|
24
|
+
from_path = opts[:from]
|
25
|
+
|
26
|
+
# True if we're initializing a project from the built-in files.
|
27
|
+
is_new = opts[:from].blank?
|
28
|
+
|
29
|
+
# Use the default, bundled path if a from-path wasn't specified.
|
30
|
+
# Verify the path exists
|
31
|
+
from_path = File.join(File.expand_path('../../..', File.dirname(__FILE__)), 'assets', 'init') if is_new
|
32
|
+
|
33
|
+
# Verify that the source directory contains the config.yml file
|
34
|
+
# signifying an existing Inkcite project.
|
35
|
+
abort "Can't find #{from_path} or it isn't an existing Inkcite project" unless File.exists?(File.join(from_path, 'config.yml'))
|
36
|
+
|
37
|
+
# Copy the main Inkcite project files
|
38
|
+
FILES.each do |file|
|
39
|
+
FileUtils.cp File.join(from_path, file), full_init_path
|
40
|
+
puts "Created #{File.join(path, file)}"
|
41
|
+
end
|
42
|
+
|
43
|
+
# Check to see if there are images and copy those as well.
|
44
|
+
from_path = File.join(from_path, Inkcite::Email::IMAGES)
|
45
|
+
if File.exists?(from_path)
|
46
|
+
FileUtils.cp_r(File.join(from_path, '.'), full_init_image_path)
|
47
|
+
puts "Copied images to #{init_image_path}"
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
FILES = %w( config.yml source.html helpers.tsv source.txt )
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'inkcite/mailer'
|
2
|
+
|
3
|
+
module Inkcite
|
4
|
+
module Cli
|
5
|
+
class Preview
|
6
|
+
|
7
|
+
def self.invoke email, to, opt
|
8
|
+
|
9
|
+
# Push the browser preview(s) up to the server to ensure that the
|
10
|
+
# latest images and "view in browser" versions are available.
|
11
|
+
email.upload
|
12
|
+
|
13
|
+
puts "Sending preview to #{to} ..."
|
14
|
+
|
15
|
+
case to.to_sym
|
16
|
+
when :client
|
17
|
+
Inkcite::Mailer.client(email)
|
18
|
+
when :internal
|
19
|
+
Inkcite::Mailer.internal(email)
|
20
|
+
when :developer
|
21
|
+
Inkcite::Mailer.developer(email)
|
22
|
+
else
|
23
|
+
raise "Invalid preview distribution target"
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'webrick'
|
2
|
+
require 'rack'
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
module Inkcite
|
6
|
+
module Cli
|
7
|
+
class Server
|
8
|
+
|
9
|
+
def self.start email, opts
|
10
|
+
|
11
|
+
# Port should always be an integer.
|
12
|
+
port = opts[:port].to_i
|
13
|
+
host = opts[:host]
|
14
|
+
|
15
|
+
# Resolve local public IP for mobile device address
|
16
|
+
ip = begin
|
17
|
+
IPSocket.getaddress(Socket.gethostname)
|
18
|
+
rescue
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
puts "Inkcite #{Inkcite::VERSION} is starting up ..."
|
23
|
+
|
24
|
+
begin
|
25
|
+
@server = ::WEBrick::HTTPServer.new({
|
26
|
+
:BindAddress => host,
|
27
|
+
:Port => port,
|
28
|
+
:AccessLog => [],
|
29
|
+
:Logger => WEBrick::Log.new(nil, 0)
|
30
|
+
})
|
31
|
+
rescue Errno::EADDRINUSE
|
32
|
+
raise "== Port #{port} is unavailable. Either close the instance of Inkcite already running on #{port} or start this Inkcite instance on a new port with: --port=#{port+1}"
|
33
|
+
exit(1)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Listen to all of the things in order to properly
|
37
|
+
# shutdown the server.
|
38
|
+
%w(INT HUP TERM QUIT).each do |sig|
|
39
|
+
if Signal.list[sig]
|
40
|
+
Signal.trap(sig) do
|
41
|
+
@server.shutdown
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
@server.mount "/", Rack::Handler::WEBrick, Inkcite::Cli::Server.new(email, opts)
|
47
|
+
|
48
|
+
puts "Your email is being served at http://#{host}:#{port}"
|
49
|
+
puts "Point your mobile device to http://#{ip}:#{port}" if ip
|
50
|
+
|
51
|
+
@server.start
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
def initialize email, opts
|
56
|
+
@email = email
|
57
|
+
@opts = opts
|
58
|
+
end
|
59
|
+
|
60
|
+
def call env
|
61
|
+
begin
|
62
|
+
|
63
|
+
path = env[REQUEST_PATH]
|
64
|
+
|
65
|
+
# If this request is for the root index page, render the email. Otherwise
|
66
|
+
# render the static asset.
|
67
|
+
if path == REQUEST_ROOT
|
68
|
+
|
69
|
+
# Check for and convert query string parameters to a symolized
|
70
|
+
# key hash so the designer can override the environment, format
|
71
|
+
# and version attributes during a given rendering.
|
72
|
+
# Courtesy of http://stackoverflow.com/questions/21990997/how-do-i-create-a-hash-from-a-querystring-in-ruby
|
73
|
+
params = CGI::parse(env[QUERY_STRING] || '')
|
74
|
+
params = Hash[params.map { |key, values| [ key.to_sym, values[0] || true ] }].symbolize_keys
|
75
|
+
|
76
|
+
# Allow the designer to specify both short- and long-form versions of
|
77
|
+
# the (e)nvironment, (f)ormat and (v)ersion. Otherwise, use the values
|
78
|
+
# that Inkcite was started with.
|
79
|
+
environment = Util.detect(params[:e], params[:environment], @opts[:environment])
|
80
|
+
format = Util.detect(params[:f], params[:format], @opts[:format])
|
81
|
+
version = Util.detect(params[:v], params[:view], @opts[:version])
|
82
|
+
|
83
|
+
# Timestamp all of the messages from this rendering so it is clear which
|
84
|
+
# messages are associated with this reload.
|
85
|
+
ts = "[#{Time.now.strftime(DATEFORMAT)}]"
|
86
|
+
|
87
|
+
puts ''
|
88
|
+
puts "#{ts} Rendering your email [environment=#{environment}, format=#{format}, version=#{version || 'default'}]"
|
89
|
+
|
90
|
+
view = @email.view(environment, format, version)
|
91
|
+
|
92
|
+
html = view.render!
|
93
|
+
|
94
|
+
unless view.errors.blank?
|
95
|
+
error_count = view.errors.count
|
96
|
+
puts "#{ts} #{error_count} error#{'s' if error_count > 1} or warning#{'s' if error_count > 1}:"
|
97
|
+
puts "#{ts} - #{view.errors.join("\n#{ts} - ")}"
|
98
|
+
end
|
99
|
+
|
100
|
+
[200, {}, [html]]
|
101
|
+
else
|
102
|
+
Rack::File.new(Dir.pwd).call(env)
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
rescue Exception => e
|
107
|
+
puts e.message
|
108
|
+
puts e.backtrace.inspect
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
REQUEST_PATH = 'REQUEST_PATH'
|
116
|
+
REQUEST_ROOT = '/'
|
117
|
+
QUERY_STRING = 'QUERY_STRING'
|
118
|
+
|
119
|
+
DATEFORMAT = '%Y-%m-%d %H:%M:%S'
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|