egalite 0.0.1 → 0.0.2
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 +4 -0
- data/LICENSE.txt +22 -0
- data/Rakefile +1 -0
- data/egalite.gemspec +24 -0
- data/lib/egalite/auth/basic.rb +32 -0
- data/lib/egalite/blank.rb +53 -0
- data/lib/egalite/errorconsole.rb +77 -0
- data/lib/egalite/helper.rb +251 -0
- data/lib/egalite/keitai/keitai.rb +107 -0
- data/lib/egalite/keitai/ketai.rb +11 -0
- data/lib/egalite/keitai/rack/ketai/carrier/abstract.rb +131 -0
- data/lib/egalite/keitai/rack/ketai/carrier/au.rb +78 -0
- data/lib/egalite/keitai/rack/ketai/carrier/docomo.rb +80 -0
- data/lib/egalite/keitai/rack/ketai/carrier/emoji/ausjisstrtoemojiid.rb +1391 -0
- data/lib/egalite/keitai/rack/ketai/carrier/emoji/docomosjisstrtoemojiid.rb +759 -0
- data/lib/egalite/keitai/rack/ketai/carrier/emoji/emojidata.rb +836 -0
- data/lib/egalite/keitai/rack/ketai/carrier/emoji/softbankutf8strtoemojiid.rb +1119 -0
- data/lib/egalite/keitai/rack/ketai/carrier/emoji/softbankwebcodetoutf8str.rb +499 -0
- data/lib/egalite/keitai/rack/ketai/carrier/iphone.rb +8 -0
- data/lib/egalite/keitai/rack/ketai/carrier/softbank.rb +82 -0
- data/lib/egalite/keitai/rack/ketai/carrier.rb +17 -0
- data/lib/egalite/keitai/rack/ketai/middleware.rb +24 -0
- data/lib/egalite/m17n.rb +193 -0
- data/lib/egalite/route.rb +231 -0
- data/lib/egalite/sendmail.rb +222 -0
- data/lib/egalite/sequel_helper.rb +20 -0
- data/lib/egalite/session.rb +132 -0
- data/lib/egalite/stringify_hash.rb +63 -0
- data/lib/egalite/support.rb +35 -0
- data/lib/egalite/template.rb +287 -0
- data/lib/egalite/version.rb +3 -0
- data/lib/egalite.rb +744 -0
- metadata +35 -3
data/lib/egalite/m17n.rb
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
|
2
|
+
module Egalite
|
3
|
+
module M17N
|
4
|
+
|
5
|
+
|
6
|
+
module Filters
|
7
|
+
def before_filter
|
8
|
+
# check hostname first to determine which language to serve.
|
9
|
+
first = req.host.split(/\./).first
|
10
|
+
@lang = Translation.lang(first)
|
11
|
+
if not @lang and req.accept_language and Translation.allow_content_negotiation
|
12
|
+
# fallback to Accept-Language HTTP header for language to serve.
|
13
|
+
langs = req.accept_language.split(/,/)
|
14
|
+
@lang = langs.map { |s| Translation.lang(s.split(/;/).first) }.compact.first
|
15
|
+
end
|
16
|
+
@lang ||= Translation.lang(Translation.user_default_lang)
|
17
|
+
@lang ||= Translation.lang('ja')
|
18
|
+
|
19
|
+
super
|
20
|
+
end
|
21
|
+
def filter_on_html_load(html,path)
|
22
|
+
html = @lang.translate_html(path, html) if @lang
|
23
|
+
super(html,path)
|
24
|
+
end
|
25
|
+
def after_filter_return_value(response)
|
26
|
+
if @lang
|
27
|
+
response = @lang.translate_msg(req.controller_class, req.action_method, response)
|
28
|
+
end
|
29
|
+
super(response)
|
30
|
+
end
|
31
|
+
def _(str, values = [])
|
32
|
+
if @lang
|
33
|
+
str = @lang.translate_string(req.controller_class, req.action_method, str, values)
|
34
|
+
else
|
35
|
+
str = str.dup
|
36
|
+
values.each_with_index { |s2,i| str.gsub!(/\{#{i}\}/, s2) }
|
37
|
+
end
|
38
|
+
str
|
39
|
+
end
|
40
|
+
end
|
41
|
+
class Controller < Egalite::Controller
|
42
|
+
include Filters
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
class Translation
|
47
|
+
class <<self
|
48
|
+
attr_accessor :langs, :user_default_lang, :allow_content_negotiation
|
49
|
+
end
|
50
|
+
def self.load(path)
|
51
|
+
@@langs = {}
|
52
|
+
|
53
|
+
s = open(path) { |f| f.read }
|
54
|
+
|
55
|
+
langs = nil
|
56
|
+
system_default = nil
|
57
|
+
|
58
|
+
[:languages, :system_default, :english_name, :native_name, :aliases].each { |optname|
|
59
|
+
s.gsub!(/\{\{#{optname}\s*(.+?)\s*\}\}\s*\n+/i) {
|
60
|
+
values = $1.split(/\s*,\s*/)
|
61
|
+
case optname
|
62
|
+
when :languages
|
63
|
+
langs = values
|
64
|
+
values.each { |lang|
|
65
|
+
@@langs[lang] = Translation.new(lang)
|
66
|
+
@@langs[lang].data = {}
|
67
|
+
}
|
68
|
+
when :system_default
|
69
|
+
lang = values.shift
|
70
|
+
@@langs[lang] = Translation.new(lang)
|
71
|
+
@@langs[lang].data = nil
|
72
|
+
system_default = lang
|
73
|
+
when :aliases
|
74
|
+
lang = values.shift
|
75
|
+
@@langs[lang].send("#{optname}=", values)
|
76
|
+
else
|
77
|
+
lang = values.shift
|
78
|
+
@@langs[lang].send("#{optname}=", values.first)
|
79
|
+
end
|
80
|
+
''
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
s.split(/###+\s*\n+/).each { |part|
|
85
|
+
next if part =~ /\A\s*\Z/
|
86
|
+
lines = part.split(/\n+/)
|
87
|
+
key = lines.shift
|
88
|
+
(type, path) = key.split(/\s+/,2)
|
89
|
+
raise "Egalite::M17N::Translation.load: type should be 'html', 'msg' or 'img' but it was '#{type}'" unless %w[msg html img].include?(type)
|
90
|
+
lines.each { |line|
|
91
|
+
if type == 'img'
|
92
|
+
langs.each { |lang|
|
93
|
+
next unless @@langs[lang].data
|
94
|
+
img = line.sub(/\.(jpg|jpeg|gif|png)/i,"_#{lang}.\\1")
|
95
|
+
@@langs[lang].data[:img] ||= {}
|
96
|
+
@@langs[lang].data[:img][path] ||= {}
|
97
|
+
@@langs[lang].data[:img][path][line] = img
|
98
|
+
}
|
99
|
+
else
|
100
|
+
a = line.split(/\s*\t+\s*/)
|
101
|
+
k = nil
|
102
|
+
a.each_with_index { |s,i|
|
103
|
+
unless @@langs[langs[i]].data
|
104
|
+
k = s
|
105
|
+
else
|
106
|
+
@@langs[langs[i]].data[type.to_sym] ||= {}
|
107
|
+
@@langs[langs[i]].data[type.to_sym][path] ||= {}
|
108
|
+
@@langs[langs[i]].data[type.to_sym][path][k] = s
|
109
|
+
end
|
110
|
+
}
|
111
|
+
end
|
112
|
+
}
|
113
|
+
}
|
114
|
+
@@langs
|
115
|
+
end
|
116
|
+
def self.lang(s)
|
117
|
+
return nil unless s
|
118
|
+
a = @@langs.find { |k,v| v.fullmatch?(s) }
|
119
|
+
a ||= @@langs.find { |k,v| v.partialmatch?(s) }
|
120
|
+
a ? a.last : nil
|
121
|
+
end
|
122
|
+
private
|
123
|
+
def method_path(c,a)
|
124
|
+
c.class.name.to_s + '#' + a.to_s
|
125
|
+
end
|
126
|
+
def t_string(list, s)
|
127
|
+
list[s] ? list[s] : s
|
128
|
+
end
|
129
|
+
def t_hash(list, h)
|
130
|
+
if h.is_a?(EgaliteResponse)
|
131
|
+
h.param = t_hash(list, h.param)
|
132
|
+
return h
|
133
|
+
end
|
134
|
+
return h unless h.is_a?(Hash)
|
135
|
+
h2 = {}
|
136
|
+
h.each { |k,v|
|
137
|
+
h2[k] = case v
|
138
|
+
when String: t_string(list,v)
|
139
|
+
when Array: v.map { |x| t_hash(list,x) }
|
140
|
+
when Hash: t_hash(list, v)
|
141
|
+
else v
|
142
|
+
end
|
143
|
+
}
|
144
|
+
h2
|
145
|
+
end
|
146
|
+
|
147
|
+
public
|
148
|
+
|
149
|
+
attr_accessor :english_name, :native_name, :aliases, :data
|
150
|
+
attr_reader :langcode
|
151
|
+
|
152
|
+
def initialize(langcode)
|
153
|
+
@langcode = langcode
|
154
|
+
@aliases = []
|
155
|
+
end
|
156
|
+
def fullmatch?(lang)
|
157
|
+
lang = lang.to_s.downcase
|
158
|
+
@langcode == lang or @aliases.include?(lang)
|
159
|
+
end
|
160
|
+
def partialmatch?(lang)
|
161
|
+
fullmatch?(lang.to_s.split(/-/).first)
|
162
|
+
end
|
163
|
+
def translate_html(path, html)
|
164
|
+
return html unless @data
|
165
|
+
list = @data[:html][path]
|
166
|
+
return html unless list
|
167
|
+
s = html.dup
|
168
|
+
list.sort { |a,b| b[0].length <=> a[0].length }.each { |k,v| s.gsub!(k, v)}
|
169
|
+
if @data[:img] and @data[:img][path]
|
170
|
+
@data[:img][path].each { |k,v| s.gsub!(k, v) }
|
171
|
+
end
|
172
|
+
s
|
173
|
+
end
|
174
|
+
def translate_msg(controller, action, msg)
|
175
|
+
return msg unless @data
|
176
|
+
list = @data[:msg][method_path(controller,action)]
|
177
|
+
return msg unless list
|
178
|
+
t_hash(list, msg)
|
179
|
+
end
|
180
|
+
def translate_string(controller, action, string, placeholders = [])
|
181
|
+
if @data
|
182
|
+
list = @data[:msg][method_path(controller,action)]
|
183
|
+
if list
|
184
|
+
string = t_string(list, string)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
string = string.dup
|
188
|
+
placeholders.each_with_index { |s2,i| string.gsub!(/\{#{i}\}/, s2) }
|
189
|
+
string
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,231 @@
|
|
1
|
+
module Egalite
|
2
|
+
class Route
|
3
|
+
def initialize(route_def)
|
4
|
+
@route = route_def
|
5
|
+
@controller = nil
|
6
|
+
@prefix = nil
|
7
|
+
@action = nil
|
8
|
+
end
|
9
|
+
def self.default_routes
|
10
|
+
routes = []
|
11
|
+
routes << Route.new([
|
12
|
+
[:controller],
|
13
|
+
[:controller],
|
14
|
+
[:action],
|
15
|
+
[:param_arg, :id],
|
16
|
+
[:params]
|
17
|
+
])
|
18
|
+
routes << Route.new([
|
19
|
+
[:controller],
|
20
|
+
[:controller],
|
21
|
+
[:param_arg, :id],
|
22
|
+
[:params]
|
23
|
+
])
|
24
|
+
routes << Route.new([
|
25
|
+
[:controller],
|
26
|
+
[:action],
|
27
|
+
[:param_arg, :id],
|
28
|
+
[:params]
|
29
|
+
])
|
30
|
+
routes << Route.new([
|
31
|
+
[:controller],
|
32
|
+
[:param_arg, :id],
|
33
|
+
[:params]
|
34
|
+
])
|
35
|
+
routes << Route.new([
|
36
|
+
[:action],
|
37
|
+
[:param_arg, :id],
|
38
|
+
[:params]
|
39
|
+
])
|
40
|
+
routes << Route.new([
|
41
|
+
[:param_arg, :id],
|
42
|
+
[:params]
|
43
|
+
])
|
44
|
+
routes
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def match(path)
|
49
|
+
path = path.sub(/^\/+/,'')
|
50
|
+
pathary = path.to_s.split('/')
|
51
|
+
controller = nil
|
52
|
+
action = nil
|
53
|
+
path_params = []
|
54
|
+
params = {}
|
55
|
+
prefix = []
|
56
|
+
@route.each { |fragment|
|
57
|
+
command = fragment[0]
|
58
|
+
|
59
|
+
case command
|
60
|
+
when :controller
|
61
|
+
if pathary.empty? and controller
|
62
|
+
controller += '/index'
|
63
|
+
elsif pathary.empty?
|
64
|
+
return nil
|
65
|
+
else
|
66
|
+
controller += '/' if controller
|
67
|
+
controller ||= ''
|
68
|
+
controller += pathary.shift
|
69
|
+
end
|
70
|
+
when :action
|
71
|
+
return nil if pathary.empty?
|
72
|
+
action = pathary.shift
|
73
|
+
when :param
|
74
|
+
next if pathary.empty?
|
75
|
+
val = pathary.shift
|
76
|
+
params[fragment[1]] = val
|
77
|
+
prefix << val unless controller
|
78
|
+
when :param_arg
|
79
|
+
next if pathary.empty?
|
80
|
+
val = pathary.shift
|
81
|
+
params[fragment[1]] = val if fragment[1]
|
82
|
+
path_params << val
|
83
|
+
prefix << val unless controller
|
84
|
+
when :param_fix
|
85
|
+
return nil if pathary[0] != fragment[2]
|
86
|
+
params[fragment[1]] = pathary.shift
|
87
|
+
prefix << val unless controller
|
88
|
+
when :controller_fix
|
89
|
+
return nil if pathary[0] != fragment[1]
|
90
|
+
controller += '/' if controller
|
91
|
+
controller ||= ''
|
92
|
+
controller += pathary.shift
|
93
|
+
when :params
|
94
|
+
path_params += pathary
|
95
|
+
pathary = []
|
96
|
+
end
|
97
|
+
}
|
98
|
+
return nil if pathary.size > 0
|
99
|
+
@controller = controller
|
100
|
+
@action = action
|
101
|
+
@path_params = path_params
|
102
|
+
@params = params
|
103
|
+
@prefix = prefix.join('/')
|
104
|
+
[controller, action, path_params, params]
|
105
|
+
end
|
106
|
+
|
107
|
+
def escape(s)
|
108
|
+
Rack::Utils.escape(s)
|
109
|
+
end
|
110
|
+
|
111
|
+
def get_path_and_params_from_params(params, current_host = nil, current_port = nil, current_scheme = nil)
|
112
|
+
route = @route || []
|
113
|
+
pathary = []
|
114
|
+
controller_exist = false
|
115
|
+
action_exist = false
|
116
|
+
contfrags = (@controller || "").to_s.split('/')
|
117
|
+
|
118
|
+
scheme = nil
|
119
|
+
host = nil
|
120
|
+
port = nil
|
121
|
+
if params[:scheme]
|
122
|
+
scheme = params[:scheme].to_s
|
123
|
+
params.delete(:scheme)
|
124
|
+
end
|
125
|
+
if params[:host]
|
126
|
+
host = params[:host]
|
127
|
+
params.delete(:host)
|
128
|
+
end
|
129
|
+
if params[:port]
|
130
|
+
port = params[:port].to_i
|
131
|
+
params.delete(:port)
|
132
|
+
end
|
133
|
+
if (scheme or port or host)
|
134
|
+
raise "get_path_and_params_from_params: current_host is not supplied." unless host or current_host
|
135
|
+
scheme = scheme || current_scheme || 'http'
|
136
|
+
prefix = "#{scheme}://#{host || current_host}"
|
137
|
+
unless (current_scheme == 'http' and current_port == 80) or (current_scheme == 'https' and current_port == 443)
|
138
|
+
port ||= current_port
|
139
|
+
end
|
140
|
+
if port
|
141
|
+
unless (scheme == 'http' and port == 80) or (scheme == 'https' and port == 443)
|
142
|
+
prefix << ":#{port}"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
route.each { |fragment|
|
148
|
+
command = fragment[0]
|
149
|
+
|
150
|
+
case command
|
151
|
+
when :controller
|
152
|
+
next if controller_exist
|
153
|
+
if params[:controller] == nil
|
154
|
+
pathary += contfrags
|
155
|
+
elsif params[:controller] =~ /^\//
|
156
|
+
pathary += params[:controller].to_s.split('/')
|
157
|
+
else
|
158
|
+
pathary += contfrags[0..-2]
|
159
|
+
pathary += params[:controller].to_s.split('/')
|
160
|
+
end
|
161
|
+
controller_exist = pathary.size
|
162
|
+
params.delete(:controller)
|
163
|
+
when :action
|
164
|
+
pathary << (params[:action] || (controller_exist ? nil : @action))
|
165
|
+
action_exist = true
|
166
|
+
params.delete(:action)
|
167
|
+
when :param
|
168
|
+
pathary << params[fragment[1]]
|
169
|
+
params.delete(fragment[1])
|
170
|
+
when :param_arg
|
171
|
+
pathary << params[fragment[1]]
|
172
|
+
params.delete(fragment[1])
|
173
|
+
when :param_fix
|
174
|
+
value = params[fragment[1]]
|
175
|
+
value = fragment[2] unless value
|
176
|
+
pathary << params[fragment[1]]
|
177
|
+
params.delete(fragment[1])
|
178
|
+
when :controller_fix
|
179
|
+
next
|
180
|
+
when :params
|
181
|
+
ary = (params[:params] || [])
|
182
|
+
if ary.respond_to?(:map)
|
183
|
+
pathary += ary.map { |s| s.to_s }
|
184
|
+
else
|
185
|
+
pathary += [ary.to_s]
|
186
|
+
end
|
187
|
+
params.delete(:params)
|
188
|
+
end
|
189
|
+
}
|
190
|
+
if not action_exist and params[:action]
|
191
|
+
if controller_exist
|
192
|
+
pathary.insert(controller_exist,params[:action])
|
193
|
+
else
|
194
|
+
pathary.unshift(params[:action])
|
195
|
+
end
|
196
|
+
params.delete(:action)
|
197
|
+
end
|
198
|
+
if not controller_exist and params[:controller]
|
199
|
+
pathary.unshift(params[:controller].to_s.split('/')).flatten!
|
200
|
+
params.delete(:controller)
|
201
|
+
end
|
202
|
+
pathary = pathary.compact.map { |frag| escape(frag) }
|
203
|
+
path = "/" + pathary.join('/').sub(/\/+$/,'').sub(/^\//,'')
|
204
|
+
|
205
|
+
[path, params, pathary, prefix]
|
206
|
+
end
|
207
|
+
|
208
|
+
def url_for(params, host = nil, port = nil, scheme = nil)
|
209
|
+
(path, params, z, prefix) = get_path_and_params_from_params(params, host, port, scheme)
|
210
|
+
if params and params.size > 0
|
211
|
+
q = []
|
212
|
+
params.each { |k,v|
|
213
|
+
if v.is_a?(Hash)
|
214
|
+
v.each { |k2,v2|
|
215
|
+
next if v2 == nil
|
216
|
+
q << "#{escape(k)}[#{escape(k2)}]=#{escape(v2)}"
|
217
|
+
}
|
218
|
+
else
|
219
|
+
q << "#{escape(k)}=#{escape(v)}" if v
|
220
|
+
end
|
221
|
+
}
|
222
|
+
path += "?" + q.join('&') unless q.empty?
|
223
|
+
end
|
224
|
+
"#{prefix}#{path}"
|
225
|
+
end
|
226
|
+
|
227
|
+
def link_to(title, params, host = nil, port = nil, scheme = nil)
|
228
|
+
"<a href='#{url_for(params, host, port, scheme)}'>#{title}</a>"
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
|
2
|
+
require 'template'
|
3
|
+
require 'nkf'
|
4
|
+
require 'time'
|
5
|
+
|
6
|
+
# mailheaders
|
7
|
+
# {
|
8
|
+
# :date => Time.new,
|
9
|
+
# :from => 'ARAI Shunichi <arai@example.com>', # encoded by Sendmail.address
|
10
|
+
# :reply_to => ,
|
11
|
+
# :to => 'tanaka@example.com', # encoded by Sendmail.address
|
12
|
+
# :cc => '', # encoded by Sendmail.address
|
13
|
+
# :bcc => '',
|
14
|
+
# :message_id => '',
|
15
|
+
# :in_reply_to => '',
|
16
|
+
# :references => '',
|
17
|
+
# :subject => '',
|
18
|
+
# }
|
19
|
+
|
20
|
+
module Sendmail
|
21
|
+
class QualifiedMailbox < String
|
22
|
+
end
|
23
|
+
class <<self
|
24
|
+
@@mock = false
|
25
|
+
def mock=(bool)
|
26
|
+
@@mock=bool
|
27
|
+
end
|
28
|
+
def folding(h, s) # folding white space. see RFC5322, section 2.3.3 and 3.2.2.
|
29
|
+
len = 78 - h.size - ": ".size
|
30
|
+
len2nd = 78 - " ".size
|
31
|
+
lines = []
|
32
|
+
line = ""
|
33
|
+
s.strip.split(/\s+/).each { |c| # each word (according to gmail's behavior)
|
34
|
+
if (line+c).size > len
|
35
|
+
len = len2nd
|
36
|
+
lines << line.strip
|
37
|
+
line = c + " "
|
38
|
+
else
|
39
|
+
line << c + " "
|
40
|
+
end
|
41
|
+
}
|
42
|
+
lines << line.strip if line.size > 0
|
43
|
+
lines.join("\n ")
|
44
|
+
end
|
45
|
+
def multibyte_folding(h, s, encoding = 'UTF-8') # RFC2047
|
46
|
+
bracketsize = "=?#{encoding}?B??=".size
|
47
|
+
len = 76 - h.size - ": ".size - bracketsize
|
48
|
+
len2nd = 76 - bracketsize
|
49
|
+
lines = []
|
50
|
+
line = ""
|
51
|
+
s = s.gsub(/\s+/, ' ').strip
|
52
|
+
s.split(//).each { |c| # each character (including multi-byte ones)
|
53
|
+
teststr = line+c
|
54
|
+
teststr = NKF.nkf('-Wj',teststr) if encoding =~ /iso-2022-jp/i
|
55
|
+
if [teststr].pack('m').chomp.size > len
|
56
|
+
len = len2nd
|
57
|
+
lines << line
|
58
|
+
line = c
|
59
|
+
else
|
60
|
+
line << c
|
61
|
+
end
|
62
|
+
}
|
63
|
+
lines << line if line.size > 0
|
64
|
+
lines = lines.map { |s| "=?#{encoding}?B?#{[s].pack('m').gsub(/\n/,'')}?=" }
|
65
|
+
lines.join("\n ")
|
66
|
+
end
|
67
|
+
def vchar(s)
|
68
|
+
s.gsub(/[\x00-\x1f\x7f]/,'')
|
69
|
+
end
|
70
|
+
def wsp(s)
|
71
|
+
s.gsub(/\s+/,' ')
|
72
|
+
end
|
73
|
+
def quote_string(s)
|
74
|
+
'"' + vchar(wsp(s)).gsub(/\\/,"\\\\\\").gsub(/\"/,'\\\"') + '"'
|
75
|
+
end
|
76
|
+
def multibyte?(s)
|
77
|
+
s.each_byte.any? { |c| c > 0x7f }
|
78
|
+
end
|
79
|
+
def encode_phrase(header, s)
|
80
|
+
if multibyte?(s)
|
81
|
+
multibyte_folding(header, s)
|
82
|
+
else
|
83
|
+
folding(header, quote_string(s))
|
84
|
+
end
|
85
|
+
end
|
86
|
+
def encode_unstructured(header, s)
|
87
|
+
if multibyte?(s)
|
88
|
+
multibyte_folding(header, s)
|
89
|
+
else
|
90
|
+
folding(header, vchar(wsp(s)))
|
91
|
+
end
|
92
|
+
end
|
93
|
+
def atext; '[0-9a-zA-Z!#$%&\'*+\-/=?\^_`{|}~]'; end
|
94
|
+
def atext_loose; '[0-9a-zA-Z!#$%&\'*+\-/=?\^_`{|}~.]'; end
|
95
|
+
|
96
|
+
def check_domain(s)
|
97
|
+
s =~ /\A#{atext}+?(\.#{atext}+?)+\Z/
|
98
|
+
end
|
99
|
+
def check_local_loose(s)
|
100
|
+
s =~ /\A#{atext_loose}+\Z/
|
101
|
+
end
|
102
|
+
def parse_addrspec(addrspec)
|
103
|
+
# no support for CFWS, FWS, and domain-literal.
|
104
|
+
if addrspec[0,1] == '"' # quoted-string
|
105
|
+
addrspec =~ /\A(\".*?[^\\]\")\@(.+)\Z/
|
106
|
+
(local, domain) = [$1, $2]
|
107
|
+
return nil if local =~ /[\x00-\x1f\x7f]/
|
108
|
+
return nil unless check_domain(domain)
|
109
|
+
[local, domain]
|
110
|
+
else
|
111
|
+
(local, domain) = addrspec.split(/@/,2)
|
112
|
+
return nil unless check_local_loose(local)
|
113
|
+
return nil unless check_domain(domain)
|
114
|
+
[local, domain]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
def address(addrspec, name = nil, header='Reply-to')
|
118
|
+
# mailbox in RFC5322 section 3.4. not 'address' as in RFC.
|
119
|
+
raise 'invalid mail address.' unless parse_addrspec(addrspec)
|
120
|
+
if name and name.size > 0
|
121
|
+
QualifiedMailbox.new(folding(header, "#{encode_phrase(header, name)} <#{addrspec}>"))
|
122
|
+
else
|
123
|
+
addrspec
|
124
|
+
end
|
125
|
+
end
|
126
|
+
def mailboxlist(value, header = 'Reply-to')
|
127
|
+
case value
|
128
|
+
when QualifiedMailbox: value
|
129
|
+
when String: parse_addrspec(value) ? value : nil
|
130
|
+
when Hash: address(value[:address],value[:name],header)
|
131
|
+
when Array
|
132
|
+
folding(header, value.map { |v|
|
133
|
+
v.is_a?(Array) ? address(v[0],v[1]) : mailboxlist(v,header)
|
134
|
+
}.join(', '))
|
135
|
+
else
|
136
|
+
nil
|
137
|
+
end
|
138
|
+
end
|
139
|
+
def message(body, params)
|
140
|
+
headers = {}
|
141
|
+
|
142
|
+
raise "From must be exist." unless params[:from]
|
143
|
+
raise "The number of sender must be zero or one." if params[:sender].is_a?(Array) and params[:sender].size > 1
|
144
|
+
raise "When the number of 'from' is more than one, sender must be exist" if params[:from].is_a?(Array) and params[:from].size > 1 and not params[:sender]
|
145
|
+
|
146
|
+
%w[From To Sender Reply-To Cc].each { |s|
|
147
|
+
v = params[s.gsub(/-/,'_').downcase.to_sym]
|
148
|
+
headers[s] = mailboxlist(v,s) if v and v.size >= 1
|
149
|
+
}
|
150
|
+
|
151
|
+
headers["Subject"] = encode_unstructured("Subject",params[:subject].to_s) if params[:subject]
|
152
|
+
headers["MIME-Version"] = "1.0"
|
153
|
+
date = params[:date] || Time.now
|
154
|
+
headers["Date"] = date.is_a?(Time) ? date.rfc822 : date
|
155
|
+
headers["Content-Type"] = "text/plain; charset=UTF-8"
|
156
|
+
|
157
|
+
if multibyte?(body)
|
158
|
+
headers["Content-Transfer-Encoding"] = "base64"
|
159
|
+
body = [body].pack('m')
|
160
|
+
else
|
161
|
+
headers["Content-Transfer-Encoding"] = "7bit"
|
162
|
+
end
|
163
|
+
|
164
|
+
text = [headers.map{|k,v| "#{k}: #{v}"}.join("\n"),body].join("\n\n")
|
165
|
+
end
|
166
|
+
private
|
167
|
+
def _extract_addrspec(value)
|
168
|
+
case value
|
169
|
+
when QualifiedMailbox
|
170
|
+
value =~ /<(#{atext_loose}+?@#{atext_loose}+?)>\Z/
|
171
|
+
$1
|
172
|
+
when String: parse_addrspec(value) ? value : nil
|
173
|
+
when Hash: parse_addrspec(value[:address]) ? value[:address] : nil
|
174
|
+
when Array
|
175
|
+
value.map { |v|
|
176
|
+
if v.is_a?(Array)
|
177
|
+
parse_addrspec(v[0]) ? v[0] : nil
|
178
|
+
else
|
179
|
+
_extract_addrspec(v)
|
180
|
+
end
|
181
|
+
}
|
182
|
+
else nil
|
183
|
+
end
|
184
|
+
end
|
185
|
+
public
|
186
|
+
def to_addresses(params)
|
187
|
+
addresses = [:to, :cc, :bcc].map { |s|
|
188
|
+
_extract_addrspec(params[s])
|
189
|
+
}
|
190
|
+
addresses.flatten.compact.uniq
|
191
|
+
end
|
192
|
+
def _send(text, envelope_from, to, host = 'localhost')
|
193
|
+
if @@mock
|
194
|
+
@@lastmail = [text, envelope_from, to, host]
|
195
|
+
else
|
196
|
+
Net::SMTP.start(host) { |smtp|
|
197
|
+
smtp.send_message(text, envelope_from, to)
|
198
|
+
}
|
199
|
+
end
|
200
|
+
end
|
201
|
+
def lastmail
|
202
|
+
@@lastmail if @@lastmail
|
203
|
+
end
|
204
|
+
def send(body, params, host = 'localhost')
|
205
|
+
_send(
|
206
|
+
message(body, params),
|
207
|
+
_extract_addrspec(params[:envelope_from] || params[:sender] || params[:from]),
|
208
|
+
to_addresses(params),
|
209
|
+
host
|
210
|
+
)
|
211
|
+
end
|
212
|
+
def send_with_template(filename, params, host = 'localhost')
|
213
|
+
File.open("mail/"+ filename ,"r") { |f|
|
214
|
+
text = f.read
|
215
|
+
tengine = Egalite::HTMLTemplate.new
|
216
|
+
tengine.default_escape = false
|
217
|
+
text = tengine.handleTemplate(text,params)
|
218
|
+
send(text, params, host)
|
219
|
+
}
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
class Sequel::Model
|
3
|
+
def update_with(hash, *selection)
|
4
|
+
data = {}
|
5
|
+
selection.flatten.each { |k| data[k] = hash[k] || hash[k.to_sym] }
|
6
|
+
update(data)
|
7
|
+
end
|
8
|
+
def update_without(hash, *selection)
|
9
|
+
hash = hash.clone
|
10
|
+
selection.flatten.each { |k| hash.delete(k.to_s) if hash.key?(k.to_s) }
|
11
|
+
selection.flatten.each { |k| hash.delete(k.to_sym) if hash.key?(k.to_sym) }
|
12
|
+
update(hash)
|
13
|
+
end
|
14
|
+
def to_hash
|
15
|
+
hash = {}
|
16
|
+
self.each { |k,v| hash[k] = v }
|
17
|
+
hash
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|