talktome 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 868359e1f73e9fcf52180d2217179b7ea15ad990
4
+ data.tar.gz: 49a82192411f5ca01f129e3ece14712ce1eee0be
5
+ SHA512:
6
+ metadata.gz: b485f9ec89169ba868237e9007cf9d2f46cb42fbffb5ed28e1565a5193b3e4d2047b48284343a1ca395b370cd046156479fea710784614a404268d31382ad0f6
7
+ data.tar.gz: ccb84f63e28059def408fa31365913f993cde2335b8ec62874e8de180bfa493a37fec596cddaca1765456c6ce8cf0d44eb0da39ea49daa6cb4439232226cd05e
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gemspec
data/LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+ # The MIT Licence
2
+
3
+ Copyright (c) 2017 - Enspirit SPRL (Bernard Lambeau)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # Talktome - Talk to users easily
2
+
3
+ ...
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'path'
2
+
3
+ #
4
+ # Install all tasks found in tasks folder
5
+ #
6
+ # See .rake files there for complete documentation.
7
+ #
8
+ Dir["tasks/*.rake"].each do |taskfile|
9
+ load taskfile
10
+ end
11
+
12
+ task :default => :test
data/lib/talktome.rb ADDED
@@ -0,0 +1,17 @@
1
+ require 'yaml'
2
+ require 'path'
3
+ require 'mustache'
4
+ require 'redcarpet'
5
+ module Talktome
6
+
7
+ def redcarpet
8
+ @redcarpet ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML, extensions = {})
9
+ end
10
+ module_function :redcarpet
11
+
12
+ end
13
+ require 'talktome/version'
14
+ require 'talktome/error'
15
+ require 'talktome/strategy'
16
+ require 'talktome/message'
17
+ require 'talktome/client'
@@ -0,0 +1,21 @@
1
+ module Talktome
2
+ class Client
3
+
4
+ def initialize
5
+ @strategies = {}
6
+ yield self if block_given?
7
+ end
8
+
9
+ def strategy(name, stragegy)
10
+ @strategies[name] = stragegy
11
+ end
12
+
13
+ protected
14
+
15
+ def get_handler(strategy)
16
+ @strategies[strategy]
17
+ end
18
+
19
+ end # class Client
20
+ end # module Talktome
21
+ require 'talktome/client/local'
@@ -0,0 +1,54 @@
1
+ module Talktome
2
+ class Client
3
+ class Local < Client
4
+
5
+ def initialize(folder, options = {})
6
+ raise ArgumentError, "Missing messages folder" unless folder
7
+ raise ArgumentError, "Invalid messages folder" unless Path(folder).directory?
8
+ @folder = folder
9
+ @options = options
10
+ super()
11
+ end
12
+ attr_reader :folder, :options
13
+
14
+ def talktome(message, user, tpldata, strategies)
15
+ message, handler = load_message!(message, strategies)
16
+ message = message.instantiate(tpldata)
17
+ options[:debugger].call(message, user, handler) if options[:debugger]
18
+ handler.send_message message, user
19
+ end
20
+
21
+ protected
22
+
23
+ def load_message!(identifier, strategies)
24
+ folder = self.folder/identifier
25
+ raise InvalidMessageError, "No such message `#{identifier}`" unless folder.exists?
26
+ raise InvalidMessageError, "Message `#{identifier}` should be a folder" unless folder.directory?
27
+ strategies.each do |s|
28
+ if (file = folder.glob("#{s}.*").first) && file.file?
29
+ handler = get_handler(s)
30
+ options = {}
31
+ options[:templater] = templater(s)
32
+ message = Message.new(file, options)
33
+ return [ message, handler ]
34
+ end
35
+ end
36
+ files = folder.glob("*").map{|f| f.basename.to_s }
37
+ raise InvalidMessageError, "No available strategy for `#{identifier}`\n#{files.inspect} vs. #{strategies.inspect}"
38
+ end
39
+
40
+ def templater(strategy)
41
+ return nil unless tpl_folder = options[:templates]
42
+ ->(message, src, ctype) {
43
+ if (file = tpl_folder/"#{strategy}.#{ctype}").file?
44
+ data = { metadata: message.metadata, yield: src }
45
+ Mustache.render(file.read, data)
46
+ else
47
+ src
48
+ end
49
+ }
50
+ end
51
+
52
+ end # class Client
53
+ end # class Client
54
+ end # module Talktome
@@ -0,0 +1,4 @@
1
+ module Talktome
2
+ class Error < StandardError; end
3
+ class InvalidMessageError < Error; end
4
+ end
@@ -0,0 +1,89 @@
1
+ module Talktome
2
+ class Message
3
+
4
+ def initialize(path, options = {})
5
+ @path = path
6
+ @options = options
7
+ compile
8
+ end
9
+ attr_accessor :path
10
+ attr_accessor :metadata
11
+ attr_accessor :data
12
+ attr_accessor :template_content
13
+ protected :path=, :metadata=, :data=, :template_content=
14
+
15
+ def instantiate(tpldata)
16
+ self.dup do |m|
17
+ m.metadata = {}
18
+ self.metadata.each_pair do |k, v|
19
+ m.metadata[k] = i(v, tpldata)
20
+ end
21
+ m.template_content = i(m.template_content, tpldata.merge(metadata: m.metadata))
22
+ m.data = tpldata
23
+ end
24
+ end
25
+
26
+ def dup
27
+ d = super
28
+ yield(d) if block_given?
29
+ d
30
+ end
31
+
32
+ def extension
33
+ path.ext.to_s
34
+ end
35
+
36
+ def to_text
37
+ template_it(self.template_content, :text)
38
+ end
39
+
40
+ def to_html
41
+ case extension
42
+ when 'md'
43
+ template_it(Talktome.redcarpet.render(self.template_content), :html)
44
+ else
45
+ template_it(self.template_content, :html)
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ class Template < Mustache
52
+
53
+ def partial(name)
54
+ path = "#{@options[:template_path]}/#{name}.#{template_extension}"
55
+ File.read(path).force_encoding(Encoding::UTF_8)
56
+ end
57
+
58
+ def raise_on_context_miss?
59
+ true
60
+ end
61
+
62
+ end
63
+
64
+ def template_it(src, ctype)
65
+ if @options[:templater]
66
+ @options[:templater].call(self, src, ctype)
67
+ else
68
+ src
69
+ end
70
+ end
71
+
72
+ def i(tpl, tpldata)
73
+ Template.new({
74
+ template_file: self.path,
75
+ template_path: self.path.parent
76
+ }).render(tpl, tpldata)
77
+ end
78
+
79
+ def compile
80
+ raw = path.read.force_encoding(Encoding::UTF_8)
81
+ if raw =~ /^(---\s*\n.*?\n?)^(---\s*$\n?)/m
82
+ @metadata, @template_content = YAML::load($1), $'
83
+ else
84
+ @metadata, @template_content = {}, raw
85
+ end
86
+ end
87
+
88
+ end # module Message
89
+ end # module Talktome
@@ -0,0 +1,7 @@
1
+ module Talktome
2
+ class Strategy
3
+
4
+ end # class Strategy
5
+ end # module Talktome
6
+ require 'talktome/strategy/debug'
7
+ require 'talktome/strategy/email'
@@ -0,0 +1,21 @@
1
+ module Talktome
2
+ class Strategy
3
+ class Debug
4
+
5
+ attr_accessor :last
6
+
7
+ def clear!
8
+ @last = nil
9
+ end
10
+
11
+ def send_message(message, user)
12
+ require 'ostruct'
13
+ @last = OpenStruct.new({
14
+ message: message,
15
+ user: user
16
+ })
17
+ end
18
+
19
+ end # class Debug
20
+ end # class Strategy
21
+ end # module Talktome
@@ -0,0 +1,46 @@
1
+ require 'mail'
2
+ module Talktome
3
+ class Strategy
4
+ class Email < Strategy
5
+
6
+ def initialize(options = {}, &defaulter)
7
+ @options = options
8
+ @defaulter = defaulter
9
+ end
10
+
11
+ def send_message(message, user)
12
+ mail = base_email
13
+ mail.to = user[:email]
14
+ mail.reply_to = message.metadata["reply_to"] if message.metadata.has_key?("reply_to")
15
+ mail.subject = message.metadata["subject"]
16
+
17
+ case message.extension
18
+ when 'md', 'html', 'htm'
19
+ mail.text_part do
20
+ content_type 'text/plain; charset=UTF-8'
21
+ body message.to_text
22
+ end
23
+ mail.html_part do
24
+ content_type 'text/html; charset=UTF-8'
25
+ body message.to_html
26
+ end
27
+ when 'text'
28
+ mail.body message.to_text
29
+ else
30
+ raise "Unsupported extension `#{message.extension}`"
31
+ end
32
+
33
+ mail.deliver!
34
+ end
35
+
36
+ private
37
+
38
+ def base_email
39
+ default_email = Mail.new
40
+ @defaulter.call(default_email) if @defaulter
41
+ default_email
42
+ end
43
+
44
+ end # class Email
45
+ end # class Strategy
46
+ end # module Talktome
@@ -0,0 +1,8 @@
1
+ module Talktome
2
+ module Version
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ TINY = 0
6
+ end
7
+ VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
8
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+ module Talktome
3
+ class Client
4
+ describe Local do
5
+
6
+ let(:strategy) {
7
+ Strategy::Debug.new
8
+ }
9
+
10
+ let(:client){
11
+ Local.new(folder, options) do |c|
12
+ c.strategy :email, strategy
13
+ end
14
+ }
15
+
16
+ let(:folder) {
17
+ Path.dir/"../fixtures"
18
+ }
19
+
20
+ let(:user) {
21
+ { email: "user@test.com" }
22
+ }
23
+
24
+ let(:tpldata) {
25
+ { who: "Test user" }
26
+ }
27
+
28
+ before(:each) {
29
+ strategy.clear!
30
+ }
31
+
32
+ context "without templates" do
33
+ let(:options) {
34
+ {}
35
+ }
36
+
37
+ it 'sends email when requested' do
38
+ client.talktome("welcome", user, tpldata, [:email])
39
+ expect(strategy.last.message).not_to be_nil
40
+ expect(strategy.last.message.to_html).to eql("<h1>Hello Test user</h1>\n\n<p>Welcome to this email example!</p>\n\n<h3>Test user</h3>\n")
41
+ end
42
+ end
43
+
44
+ context "with templates" do
45
+ let(:options) {
46
+ {
47
+ templates: Path.dir/"../fixtures/templates"
48
+ }
49
+ }
50
+
51
+ it 'sends email when requested' do
52
+ client.talktome("welcome", user, tpldata, [:email])
53
+ expect(strategy.last.message).not_to be_nil
54
+ expect(strategy.last.message.to_html).to eql("<html><title>Hello Test user</title><body><h1>Hello Test user</h1>\n\n<p>Welcome to this email example!</p>\n\n<h3>Test user</h3>\n</body></html>\n")
55
+ end
56
+ end
57
+
58
+ end
59
+ end
60
+ end
@@ -0,0 +1 @@
1
+ <html><title>{{metadata.subject}}</title><body>{{{yield}}}</body></html>
@@ -0,0 +1,8 @@
1
+ ---
2
+ subject: Hello {{who}}
3
+ ---
4
+ # {{metadata.subject}}
5
+
6
+ Welcome to this email example!
7
+
8
+ {{> footer}}
@@ -0,0 +1 @@
1
+ ### {{who}}
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+ module Talktome
3
+ describe Message, 'initialize' do
4
+
5
+ subject{
6
+ Message.new path
7
+ }
8
+
9
+ context 'on a file with yaml frontmatter' do
10
+ let(:path) { Path.dir/'with-frontmatter.md' }
11
+
12
+ it 'works as expected' do
13
+ expect(subject.template_content).to eql("Subject: {{metadata.subject}}\n\nHello {{who}} [test]({{url}})\n")
14
+ expect(subject.metadata).to eql("hello" => "World", "subject" => "Hello {{who}}")
15
+ end
16
+ end
17
+
18
+ context 'on a file without yaml frontmatter' do
19
+ let(:path) { Path.dir/'without-frontmatter.md' }
20
+
21
+ it 'works as expected' do
22
+ expect(subject.template_content).to eql("Hello {{who}}\n")
23
+ expect(subject.metadata).to eql({})
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+ module Talktome
3
+ describe Message, 'instantiate' do
4
+
5
+ subject{
6
+ Message.new(path).instantiate(data)
7
+ }
8
+
9
+ let(:path) { Path.dir/'with-frontmatter.md' }
10
+ let(:data) { { url: "http://foo", who: "bar"} }
11
+
12
+ it 'works as expected' do
13
+ expect(subject.template_content).to eql("Subject: Hello bar\n\nHello bar [test](http://foo)\n")
14
+ expect(subject.metadata).to eql("hello" => "World", "subject" => "Hello bar")
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,7 @@
1
+ ---
2
+ hello: World
3
+ subject: Hello {{who}}
4
+ ---
5
+ Subject: {{metadata.subject}}
6
+
7
+ Hello {{who}} [test]({{url}})
@@ -0,0 +1 @@
1
+ Hello {{who}}
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'talktome'
3
+
4
+ module SpecHelpers
5
+ end
6
+
7
+ RSpec.configure do |c|
8
+ c.include SpecHelpers
9
+ end
data/tasks/gem.rake ADDED
@@ -0,0 +1,39 @@
1
+ require 'rubygems/package_task'
2
+
3
+ # Dynamically load the gem spec
4
+ gemspec_file = File.expand_path('../../talktome.gemspec', __FILE__)
5
+ gemspec = Kernel.eval(File.read(gemspec_file))
6
+
7
+ Gem::PackageTask.new(gemspec) do |t|
8
+
9
+ # Name of the package
10
+ t.name = gemspec.name
11
+
12
+ # Version of the package
13
+ t.version = gemspec.version
14
+
15
+ # Directory used to store the package files
16
+ t.package_dir = "pkg"
17
+
18
+ # True if a gzipped tar file (tgz) should be produced
19
+ t.need_tar = false
20
+
21
+ # True if a gzipped tar file (tar.gz) should be produced
22
+ t.need_tar_gz = false
23
+
24
+ # True if a bzip2'd tar file (tar.bz2) should be produced
25
+ t.need_tar_bz2 = false
26
+
27
+ # True if a zip file should be produced (default is false)
28
+ t.need_zip = false
29
+
30
+ # List of files to be included in the package.
31
+ t.package_files = gemspec.files
32
+
33
+ # Tar command for gzipped or bzip2ed archives.
34
+ t.tar_command = "tar"
35
+
36
+ # Zip command for zipped archives.
37
+ t.zip_command = "zip"
38
+
39
+ end
data/tasks/test.rake ADDED
@@ -0,0 +1,11 @@
1
+ namespace :test do
2
+ require "rspec/core/rake_task"
3
+
4
+ desc "Run RSpec code examples"
5
+ RSpec::Core::RakeTask.new(:unit) do |t|
6
+ t.pattern = "spec/**/test_*.rb"
7
+ t.rspec_opts = ["--color", "--backtrace"]
8
+ end
9
+
10
+ end
11
+ task :test => [:'test:unit']
metadata ADDED
@@ -0,0 +1,170 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: talktome
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Bernard Lambeau
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-03-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.6'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: path
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '1.3'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '1.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: mail
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2'
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 2.6.6
65
+ type: :runtime
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - "~>"
70
+ - !ruby/object:Gem::Version
71
+ version: '2'
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 2.6.6
75
+ - !ruby/object:Gem::Dependency
76
+ name: mustache
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '1'
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: 1.0.5
85
+ type: :runtime
86
+ prerelease: false
87
+ version_requirements: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - "~>"
90
+ - !ruby/object:Gem::Version
91
+ version: '1'
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: 1.0.5
95
+ - !ruby/object:Gem::Dependency
96
+ name: redcarpet
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: '3'
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '3.4'
105
+ type: :runtime
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: '3'
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '3.4'
115
+ description: Talktome helps you talk to users by email, messaging, sms, etc. It abstracts
116
+ the messaging mechanisms and lets you manage message templates easily.
117
+ email: blambeau@gmail.com
118
+ executables: []
119
+ extensions: []
120
+ extra_rdoc_files: []
121
+ files:
122
+ - Gemfile
123
+ - LICENSE.md
124
+ - README.md
125
+ - Rakefile
126
+ - lib/talktome.rb
127
+ - lib/talktome/client.rb
128
+ - lib/talktome/client/local.rb
129
+ - lib/talktome/error.rb
130
+ - lib/talktome/message.rb
131
+ - lib/talktome/strategy.rb
132
+ - lib/talktome/strategy/debug.rb
133
+ - lib/talktome/strategy/email.rb
134
+ - lib/talktome/version.rb
135
+ - spec/client/test_local.rb
136
+ - spec/fixtures/templates/email.html
137
+ - spec/fixtures/welcome/email.md
138
+ - spec/fixtures/welcome/footer.mustache
139
+ - spec/message/test_initialize.rb
140
+ - spec/message/test_instantiate.rb
141
+ - spec/message/with-frontmatter.md
142
+ - spec/message/without-frontmatter.md
143
+ - spec/spec_helper.rb
144
+ - tasks/gem.rake
145
+ - tasks/test.rake
146
+ homepage: http://github.com/enspirit/talktome
147
+ licenses:
148
+ - MIT
149
+ metadata: {}
150
+ post_install_message:
151
+ rdoc_options: []
152
+ require_paths:
153
+ - lib
154
+ required_ruby_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ required_rubygems_version: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - ">="
162
+ - !ruby/object:Gem::Version
163
+ version: '0'
164
+ requirements: []
165
+ rubyforge_project:
166
+ rubygems_version: 2.5.2.3
167
+ signing_key:
168
+ specification_version: 4
169
+ summary: Talktome helps you talk to users by email, messaging, sms, etc.
170
+ test_files: []