inkcite 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|