nekonote-framework 1.0.0.pre.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +49 -0
  4. data/bin/nekonote +45 -0
  5. data/data/structure/Gemfile +25 -0
  6. data/data/structure/config.ru +14 -0
  7. data/data/structure/handler/base.rb +21 -0
  8. data/data/structure/handler/error.rb +35 -0
  9. data/data/structure/handler/welcome.rb +11 -0
  10. data/data/structure/lib/.gitkeep +0 -0
  11. data/data/structure/preference/development/logger.yml +62 -0
  12. data/data/structure/preference/development/middlewares.rb +152 -0
  13. data/data/structure/preference/development/public.yml +29 -0
  14. data/data/structure/preference/development/route.yml +30 -0
  15. data/data/structure/preference/development/route_error.yml +28 -0
  16. data/data/structure/preference/development/route_include.yml +22 -0
  17. data/data/structure/preference/development/server/puma.rb +63 -0
  18. data/data/structure/preference/development/setting/example.yml +40 -0
  19. data/data/structure/preference/development/setting/site.yml +3 -0
  20. data/data/structure/preference/development/setting/welcome.yml +7 -0
  21. data/data/structure/public/css/layout/common.css +11 -0
  22. data/data/structure/public/css/layout/default.css +3 -0
  23. data/data/structure/public/css/layout/error.css +3 -0
  24. data/data/structure/public/css/welcome.css +47 -0
  25. data/data/structure/public/favicon.ico +0 -0
  26. data/data/structure/public/img/.gitkeep +0 -0
  27. data/data/structure/public/img/logo.png +0 -0
  28. data/data/structure/public/js/.gitkeep +0 -0
  29. data/data/structure/static/layout/default.tpl +19 -0
  30. data/data/structure/static/layout/error.tpl +15 -0
  31. data/data/structure/static/sass/welcome.scss +52 -0
  32. data/data/structure/static/template/error.tpl +4 -0
  33. data/data/structure/static/template/welcome/index.tpl +26 -0
  34. data/data/structure/tmp/pids/.gitkeep +0 -0
  35. data/lib/loader.rb +83 -0
  36. data/lib/nekonote.rb +9 -0
  37. data/lib/nekonote/cli.rb +702 -0
  38. data/lib/nekonote/cmd_parser.rb +55 -0
  39. data/lib/nekonote/core.rb +116 -0
  40. data/lib/nekonote/env.rb +56 -0
  41. data/lib/nekonote/exception/cli_error.rb +34 -0
  42. data/lib/nekonote/exception/error.rb +75 -0
  43. data/lib/nekonote/exception/handler_error.rb +5 -0
  44. data/lib/nekonote/exception/logger_error.rb +8 -0
  45. data/lib/nekonote/exception/page_cache_error.rb +6 -0
  46. data/lib/nekonote/exception/preference_error.rb +11 -0
  47. data/lib/nekonote/exception/view_error.rb +7 -0
  48. data/lib/nekonote/handler.rb +274 -0
  49. data/lib/nekonote/handler/protected_methods.rb +119 -0
  50. data/lib/nekonote/liquid/tag_env_get.rb +12 -0
  51. data/lib/nekonote/liquid/tag_setting_get.rb +12 -0
  52. data/lib/nekonote/logger.rb +135 -0
  53. data/lib/nekonote/page_cache.rb +111 -0
  54. data/lib/nekonote/preference.rb +215 -0
  55. data/lib/nekonote/puma.rb +131 -0
  56. data/lib/nekonote/rack/rack_static.rb +17 -0
  57. data/lib/nekonote/rack/rack_static_file.rb +19 -0
  58. data/lib/nekonote/rack/url_mapper.rb +193 -0
  59. data/lib/nekonote/rackup.rb +319 -0
  60. data/lib/nekonote/request.rb +295 -0
  61. data/lib/nekonote/setting.rb +59 -0
  62. data/lib/nekonote/spec.rb +22 -0
  63. data/lib/nekonote/util/filer.rb +69 -0
  64. data/lib/nekonote/util/process.rb +43 -0
  65. data/lib/nekonote/view.rb +398 -0
  66. data/lib/nekonote/yaml_access.rb +60 -0
  67. metadata +144 -0
@@ -0,0 +1,131 @@
1
+ module Nekonote
2
+ class Puma
3
+ # @param string app_root
4
+ # @param string env
5
+ def initialize(app_root, env)
6
+ @app_root = app_root
7
+ @env = env
8
+ end
9
+
10
+ # @return string
11
+ public
12
+ def get_config_path
13
+ return get_puma_config_file_path
14
+ end
15
+
16
+ # @param cmd symbol
17
+ public
18
+ def ctl_server(cmd)
19
+ # set pumactl command corresponded with nekonote sub command
20
+ case cmd
21
+ when :start
22
+ puma_cmd = 'start'
23
+
24
+ when :status
25
+ puma_cmd = 'status'
26
+
27
+ when :stop
28
+ puma_cmd = 'stop'
29
+
30
+ when :halt
31
+ puma_cmd = 'halt'
32
+
33
+ when :restart
34
+ puma_cmd = 'restart'
35
+
36
+ when :phased_restart
37
+ puma_cmd = 'phased-restart'
38
+ end
39
+
40
+ # make object
41
+ argv = ['-F', get_puma_config_file_path, puma_cmd]
42
+ stdout_buffer = $stdout.clone
43
+ stderr_buffer = $stderr.clone
44
+
45
+ begin
46
+ if cmd == :start
47
+ change_exec_file_to_puma_bin # if did't change $0 to puma/bin/puma, restart will be failed
48
+ end
49
+ cli = ::Puma::ControlCLI.new argv, STDOUT, STDERR
50
+
51
+ # get pid if it exists
52
+ def cli.get_pid_file_path
53
+ return @pidfile
54
+ end
55
+ pid = Util::Process.get_server_pid cli.get_pid_file_path
56
+
57
+ # exit if there's no need to run Puma::ControlCLI
58
+ nothing_to_do = false
59
+ case cmd
60
+ when :start
61
+ if pid != nil
62
+ puts %(Already started with pid #{pid})
63
+ nothing_to_do = true
64
+ end
65
+
66
+ when :status
67
+ if pid == nil
68
+ puts %(Server is stopped)
69
+ else
70
+ puts %(Server is running with pid #{pid})
71
+ end
72
+ nothing_to_do = true
73
+
74
+ when :stop, :halt
75
+ if pid == nil
76
+ puts %(Already stopped)
77
+ nothing_to_do = true
78
+ end
79
+
80
+ when :restart, :phased_restart
81
+ if pid == nil
82
+ # it have not started!
83
+ ctl_server :start
84
+ nothing_to_do = true
85
+ end
86
+ end
87
+
88
+ # exit if no need to continue task
89
+ if nothing_to_do
90
+ $stdout = stdout_buffer
91
+ $stderr = stderr_buffer
92
+ exit 0
93
+ end
94
+
95
+ # send signal
96
+ # when requested 'start' it will exit here
97
+ cli.run
98
+
99
+ ensure
100
+ # it won't called if script exited
101
+ $stdout = stdout_buffer
102
+ $stderr = stderr_buffer
103
+ end
104
+ end
105
+
106
+ private
107
+ def change_exec_file_to_puma_bin
108
+ puma_bin_path = "#{::Bundler.bundle_path.to_s}/gems/puma-#{::Puma::Const::VERSION}/bin/puma"
109
+
110
+ # is it readable?
111
+ if !Util::Filer.available_file? puma_bin_path
112
+ raise PreferenceError, Error::MSG_MISSING_FILE% puma_bin_path
113
+ end
114
+
115
+ $0 = puma_bin_path
116
+ end
117
+
118
+ # @return string
119
+ private
120
+ def get_puma_config_file_path
121
+ file_path = "#{@app_root}/preference/#{@env}/server/puma.rb"
122
+
123
+ # is it readable?
124
+ if !Util::Filer.available_file? file_path
125
+ raise PreferenceError, Error::MSG_MISSING_FILE% file_path
126
+ end
127
+
128
+ return file_path
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,17 @@
1
+ module Nekonote
2
+ class RackStatic < ::Rack::Static
3
+ def initialize(app, options={})
4
+ if options[:root] == nil
5
+ raise Error, self.class.to_s + ' require key :root'
6
+ end
7
+
8
+ super
9
+
10
+ # Overwrite property for using Nekonote::RackStaticFile instead of Rack::File.
11
+ # This for handling the error case that file requested was not found.
12
+ @file_server = RackStaticFile.new options[:root]
13
+
14
+ # @file_server = Rack::File.new(root)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ module Nekonote
2
+ class RackStaticFile < ::Rack::File
3
+ def fail(status, body, headers = {})
4
+ if Preference.instance.has_error_route? Preference::FIELD_ROUTE_ERR_NOT_FOUND
5
+ begin
6
+ # display custom error response
7
+ return ::Nekonote::Handler.call_error_handler Preference::FIELD_ROUTE_ERR_NOT_FOUND, Env.get_all
8
+ rescue => e
9
+ Error.logging_error e
10
+ # error, default behavior
11
+ super
12
+ end
13
+ else
14
+ # no error route, default behavior
15
+ super
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,193 @@
1
+ # This class is forked from rack/urlmap.rb
2
+ # Constant values in core source must be prefixed namespace.
3
+ # Allows using 2-space indentation in this class because of Rack using 2-space indentation
4
+ module Nekonote
5
+ class URLMapper < ::Rack::URLMap
6
+ # =========================================================================
7
+ # Start adding source code for Nekonote Framework
8
+ # =========================================================================
9
+ # @param string pattern
10
+ # @returns hash, string
11
+ def parse_url_path_params(pattern)
12
+ url_path_params_mapper = {}
13
+ # change variables in path to wild card
14
+ if /:.+/ =~ pattern
15
+ pattern.split('/').each_with_index do |inspection, index|
16
+ inspection.scan(/(?<=:).+/).each do |v|
17
+ url_path_params_mapper[v] = index
18
+ end
19
+ end
20
+
21
+ # replace variable with wildcard
22
+ url_path_params_mapper.each_key do |name|
23
+ pattern.sub! (':' + name), '.+'
24
+ end
25
+ end
26
+
27
+ return url_path_params_mapper, pattern
28
+ end
29
+
30
+ # @param string pattern
31
+ # @returns regexp, hash, string
32
+ def get_route_regexp(pattern)
33
+ # if home page
34
+ pattern = '/' if pattern == ''
35
+
36
+ # escape special meaning characters in regexp
37
+ pattern = Regexp.quote pattern
38
+
39
+ # parse path for url path parameters
40
+ url_path_params_mapper, pattern = parse_url_path_params pattern
41
+
42
+ pattern = %(^#{pattern}$)
43
+
44
+ # If duplocate slashes are allowed change regexp a little bit for it
45
+ if Preference.instance.is_allow_dup_slash?
46
+ pattern.gsub! '/', '/+'
47
+ end
48
+
49
+ match = Regexp.new pattern, nil, 'n'
50
+
51
+ return match, url_path_params_mapper, pattern
52
+ end
53
+
54
+ # @param string pattern
55
+ # @returns regexp, hash, string
56
+ def get_route_regexp_custom(pattern)
57
+ option = nil
58
+ code = nil
59
+
60
+ # parse path for url path parameters
61
+ url_path_params_mapper, pattern = parse_url_path_params pattern
62
+
63
+ if pattern == ''
64
+ # home page
65
+ pattern = '/$'
66
+
67
+ elsif /\/[ixmn]+$/ =~ pattern
68
+ # there is regexp option
69
+ matched_str = $&.delete '/'
70
+ pattern = pattern.sub /\/[ixmn]+$/, ''
71
+ option = 0
72
+ matched_str.each_char do |char|
73
+ case char
74
+ when 'i'
75
+ option = option | Regexp::IGNORECASE
76
+ when 'm'
77
+ option = option | Regexp::MULTILINE
78
+ when 'x'
79
+ option = option | Regexp::EXTENDED
80
+ when 'n'
81
+ code = 'n'
82
+ end
83
+ end
84
+ end
85
+
86
+ pattern = '^' + pattern
87
+
88
+ # If duplocate slashes are allowed change regexp a little bit for it
89
+ if Preference.instance.is_allow_dup_slash?
90
+ pattern.gsub! '/', '/+'
91
+ end
92
+
93
+ match = Regexp.new pattern, option, code
94
+
95
+ return match, url_path_params_mapper, pattern
96
+ end
97
+ # =========================================================================
98
+ # End adding source code
99
+ # =========================================================================
100
+
101
+ def remap(map)
102
+ @mapping = map.map { |location, app|
103
+ if location =~ %r{\Ahttps?://(.*?)(/.*)}
104
+ host, location = $1, $2
105
+ else
106
+ host = nil
107
+ end
108
+
109
+ unless location[0] == ?/
110
+ raise ArgumentError, "paths need to start with /"
111
+ end
112
+
113
+ location = location.chomp('/')
114
+ #match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n') # comment out of original source
115
+ # =========================================================================
116
+ # Start adding source code for Nekonote Framework
117
+ # =========================================================================
118
+ # get regexp for matching URL
119
+ if Preference.instance.is_path_regexp?
120
+ match, url_path_params_mapper, location = get_route_regexp_custom location # path will be evaluated as regexp
121
+ else
122
+ match, url_path_params_mapper, location = get_route_regexp location
123
+ end
124
+
125
+ # set the values to Nekonote::Handler class
126
+ app.route_regexp = match
127
+ app.url_path_params_mapper = url_path_params_mapper
128
+ # =========================================================================
129
+ # End adding source code
130
+ # =========================================================================
131
+
132
+ [host, location, match, app]
133
+ }.sort_by do |(host, location, _, _)|
134
+ [host ? -host.size : ::Rack::URLMap::INFINITY, -location.size]
135
+ end
136
+ end
137
+
138
+ def call(env)
139
+ path = env[::Rack::PATH_INFO]
140
+ script_name = env[::Rack::SCRIPT_NAME]
141
+ http_host = env[::Rack::HTTP_HOST]
142
+ server_name = env[::Rack::SERVER_NAME]
143
+ server_port = env[::Rack::SERVER_PORT]
144
+
145
+ is_same_server = casecmp?(http_host, server_name) ||
146
+ casecmp?(http_host, "#{server_name}:#{server_port}")
147
+
148
+ @mapping.each do |host, location, match, app|
149
+ unless casecmp?(http_host, host) \
150
+ || casecmp?(server_name, host) \
151
+ || (!host && is_same_server)
152
+ next
153
+ end
154
+
155
+ next unless m = match.match(path.to_s)
156
+
157
+ rest = m[1]
158
+ next unless !rest || rest.empty? || rest[0] == ?/
159
+ env[::Rack::SCRIPT_NAME] = (script_name + location)
160
+ env[::Rack::PATH_INFO] = rest
161
+
162
+ return app.call(env)
163
+ end
164
+
165
+ # [404, {::Rack::CONTENT_TYPE => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]] comment out of original source
166
+ # =========================================================================
167
+ # Start adding source code for Nekonote Framework
168
+ # =========================================================================
169
+ if Preference.instance.has_error_route? Preference::FIELD_ROUTE_ERR_MISSING_ROUTE
170
+ # "missing_route" route has been defined
171
+ begin
172
+ # display custom error response
173
+ return ::Nekonote::Handler.call_error_handler Preference::FIELD_ROUTE_ERR_MISSING_ROUTE, env
174
+ rescue => e
175
+ Error.logging_error e
176
+ # error, default behavior
177
+ [404, {::Rack::CONTENT_TYPE => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
178
+ end
179
+
180
+ else
181
+ # no error route, default behavior
182
+ [404, {::Rack::CONTENT_TYPE => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
183
+ end
184
+ # =========================================================================
185
+ # End adding source code for Nekonote Framework
186
+ # =========================================================================
187
+
188
+ ensure
189
+ env[::Rack::PATH_INFO] = path
190
+ env[::Rack::SCRIPT_NAME] = script_name
191
+ end
192
+ end # class URLMapper
193
+ end # module Nekonote
@@ -0,0 +1,319 @@
1
+ module Nekonote
2
+ class Rackup
3
+ include Singleton
4
+
5
+ def initialize
6
+ begin
7
+ # initialize Logger
8
+ Nekonote.init_logger
9
+
10
+ # initialize Setting
11
+ Setting.init
12
+ rescue => e
13
+ Error.abort e
14
+ end
15
+ end
16
+
17
+ # @param mixed info
18
+ # @return hash
19
+ def self.get_header_rules_field(info)
20
+ headers = {}
21
+ if info.is_a? Hash
22
+ # just one header
23
+ headers = {info['name'] => info['value']}
24
+
25
+ elsif info.is_a? Array
26
+ # plural headers
27
+ headers = {}
28
+ info.each do |pair|
29
+ headers[pair['name']] = pair['value']
30
+ end
31
+ end
32
+ return headers
33
+ end
34
+
35
+ # @param mixed info
36
+ # @param proc get_rule
37
+ # @return hash
38
+ def self.get_header_rules_field_having_target(info, get_rule)
39
+ rules = []
40
+ if info.is_a?(Array)
41
+ # multiple
42
+ stack = []
43
+ info.each do |each_ext|
44
+ rule = get_rule.call each_ext
45
+ stack << rule if rule != nil
46
+ end
47
+ stack.each do |rule|
48
+ rules << rule
49
+ end
50
+ else
51
+ rule = get_rule.call info
52
+ rules << rule if rule != nil
53
+ end
54
+ return rules
55
+ end
56
+
57
+ # make data for header rules option
58
+ # @param hash info
59
+ # @return array
60
+ def self.get_header_rules(info)
61
+ rules = []
62
+
63
+ # for :all
64
+ headers = get_header_rules_field info['all']
65
+ rules << [:all, headers] if headers.size > 0
66
+
67
+ # for :directory
68
+ if info['directory'] != nil
69
+ get_rule = lambda do |data|
70
+ rule = nil
71
+ if data.is_a?(Hash) && data.has_key?('target')
72
+ dir = data.delete 'target'
73
+ dir = "/#{dir}" if !dir.start_with? '/'
74
+ headers = get_header_rules_field data
75
+ rule = [dir, headers] if headers.size > 0
76
+ end
77
+ return rule
78
+ end
79
+ rules_for_dir = get_header_rules_field_having_target info['directory'], get_rule
80
+ rules_for_dir.each do |rule|
81
+ rules << rule
82
+ end
83
+ end
84
+
85
+ # for :extension
86
+ if info['extension'] != nil
87
+ get_rule = lambda do |data|
88
+ rule = nil
89
+ if data.is_a?(Hash) && data.has_key?('target')
90
+ target = data.delete 'target'
91
+ ext = target.split(',')
92
+ ext.map! do |v| v.strip end
93
+ headers = get_header_rules_field data
94
+ rule = [ext, headers] if headers.size > 0
95
+ end
96
+ return rule
97
+ end
98
+ rules_for_dir = get_header_rules_field_having_target info['extension'], get_rule
99
+ rules_for_dir.each do |rule|
100
+ rules << rule
101
+ end
102
+ end
103
+
104
+ # for :regexp
105
+ if info['regexp'] != nil
106
+ get_rule = lambda do |data|
107
+ rule = nil
108
+ if data.is_a?(Hash) && data.has_key?('target')
109
+ target = data.delete 'target'
110
+ if data['ignore_case'] == true
111
+ regexp = Regexp.new target, Regexp::IGNORECASE
112
+ else
113
+ regexp = Regexp.new target
114
+ end
115
+ headers = get_header_rules_field data
116
+ rule = [regexp, headers] if headers.size > 0
117
+ end
118
+ return rule
119
+ end
120
+ rules_for_dir = get_header_rules_field_having_target info['regexp'], get_rule
121
+ rules_for_dir.each do |rule|
122
+ rules << rule
123
+ end
124
+ end
125
+
126
+ # for :fonts
127
+ headers = get_header_rules_field info['fonts']
128
+ rules << [:fonts, headers] if headers.size > 0
129
+
130
+ return rules
131
+ end
132
+
133
+ # @return proc
134
+ public
135
+ def use_middlewares
136
+ return Proc.new do
137
+ # overwrite the core method of lib/rack/builder.rb
138
+ def self.use(middleware, *args, &block)
139
+ @nekonote_middlewares = [] if !defined? @nekonote_middlewares
140
+ @nekonote_middlewares << middleware.to_s
141
+ super
142
+ end
143
+
144
+ # add the individual method
145
+ def self.get_nekonote_middlewares
146
+ return @nekonote_middlewares
147
+ end
148
+
149
+ # evaluate middlewares.rb as Ruby codes
150
+ path = Preference.instance.path_middlewares_rb
151
+ begin
152
+ instance_eval IO.read path
153
+ rescue => e
154
+ warn <<EOS
155
+ #{PreferenceError::MSG_EVAL_MIDDLEWARES% path}
156
+
157
+ #{e.class}:
158
+ #{e.message}
159
+
160
+ #{e.backtrace.join($/)}
161
+ EOS
162
+ exit 1
163
+ end
164
+
165
+ # display middleware list
166
+ is_enabled_show_exceptions = false
167
+ get_nekonote_middlewares.each do |middleware_name|
168
+ if middleware_name == 'Rack::ShowExceptions'
169
+ is_enabled_show_exceptions = true
170
+ end
171
+ puts " + Use -> #{middleware_name}"
172
+ end
173
+
174
+ Preference.instance.is_enabled_show_exceptions = is_enabled_show_exceptions
175
+ end
176
+ end
177
+
178
+ # publishing for static files
179
+ public
180
+ def define_public_dir
181
+ return Proc.new do |pref_public|
182
+ # expected Hash only
183
+ pref_public = {} if !pref_public.is_a? Hash
184
+
185
+ options = {
186
+ :root => Nekonote.get_root_path + 'public'
187
+ }
188
+
189
+ # publish specific files only
190
+ # define published directories under 'public'
191
+ pub_dirs = []
192
+ if pref_public['published_directory'].is_a? Array
193
+ pub_dirs = pref_public['published_directory']
194
+ pub_dirs.map! do |val|
195
+ if val.start_with? '/'
196
+ val
197
+ else
198
+ '/' + val
199
+ end
200
+ end
201
+ end
202
+ if pub_dirs.count > 0
203
+ options[:urls] = pub_dirs
204
+ end
205
+
206
+ # define published files under 'public'
207
+ pub_files = []
208
+ if pref_public['published_file'].is_a? Array
209
+ pub_files = pref_public['published_file']
210
+ pub_files.map! do |val|
211
+ if val.start_with? '/'
212
+ val
213
+ else
214
+ '/' + val
215
+ end
216
+ end
217
+ end
218
+ if pub_files.count > 0
219
+ if options[:urls].is_a? Array
220
+ options[:urls] = options[:urls] + pub_files
221
+ else
222
+ options[:urls] = pub_files
223
+ end
224
+ end
225
+
226
+ # add custom headers
227
+ if pref_public['custom_header'].is_a? Hash
228
+ rules = ::Nekonote::Rackup.get_header_rules pref_public['custom_header']
229
+ if rules.size > 0
230
+ options[:header_rules] = rules
231
+ end
232
+ end
233
+
234
+ # register published static files
235
+ use RackStatic, options
236
+ end # end proc
237
+ end
238
+
239
+ # @return proc
240
+ public
241
+ def define_route
242
+ return Proc.new do |pref_route|
243
+ # load the common handler
244
+ Nekonote.load_base_handler
245
+
246
+ # load files under 'handler' direcotry
247
+ Dir[File.expand_path('handler', Nekonote.get_root_path) + '/**/*.rb'].each do |file|
248
+ require file # TODO need auto loading
249
+ end
250
+
251
+ # get preferences for include
252
+ pref_common = Preference.instance.get_route_include
253
+
254
+ # define the routes
255
+ routes = {} # instance list of app
256
+ paths = [] # for duplicate check
257
+ pref_route.each do |info|
258
+ # if include directive has been set, convert it to the directives and missing directives will be filled
259
+ if info[Preference::FIELD_ROUTE_INCLUDE].is_a? String
260
+ # include directive has been set in the route
261
+ if pref_common[info[Preference::FIELD_ROUTE_INCLUDE]].is_a? Hash
262
+ pref_common[info[Preference::FIELD_ROUTE_INCLUDE]].each_pair do |k, v|
263
+ if info[k] == nil
264
+ info[k] = v
265
+ else
266
+ # directive name is duplicate between route.yml and route_include.yml
267
+ # values in route.yml takes precedence over values in route_include.yml that without method or params
268
+ info[k] += ',' + v if k == Preference::FIELD_ROUTE_PARAMS || k == Preference::FIELD_ROUTE_ALLOW_METHODS
269
+ end
270
+ end
271
+ info.delete Preference::FIELD_ROUTE_INCLUDE
272
+ else
273
+ # no such field in route_include.yml
274
+ raise PreferenceError, PreferenceError::MSG_MISSING_INCLUDE% [info[Preference::FIELD_ROUTE_INCLUDE], Preference.instance.path_route_include_yml]
275
+ end
276
+ end
277
+
278
+ # difined path field?
279
+ path = info[Preference::FIELD_ROUTE_PATH]
280
+ if !path.is_a? String
281
+ raise PreferenceError, PreferenceError::MSG_MISSING_FIELD% [Preference::FIELD_ROUTE_PATH, Preference.instance.path_route_yml]
282
+ end
283
+
284
+ # having handler?
285
+ handler = info[Preference::FIELD_ROUTE_HANDLER]
286
+ if !handler.is_a? String
287
+ raise PreferenceError, PreferenceError::MSG_INVALID_HANDLER_NAME% "#{handler}"
288
+ end
289
+
290
+ # validation for the field 'page_cache_time'
291
+ if info[Preference::FIELD_ROUTE_PAGE_CACHE_TIME] != nil
292
+ page_cache_time = info[Preference::FIELD_ROUTE_PAGE_CACHE_TIME].to_i
293
+ if page_cache_time > 0
294
+ info[Preference::FIELD_ROUTE_PAGE_CACHE_TIME] = page_cache_time
295
+ else
296
+ raise Error, Error::MSG_INVALID_FIELD% [Preference::FIELD_ROUTE_PAGE_CACHE_TIME, Preference.instance.path_route_yml]
297
+ end
298
+ end
299
+
300
+ # set app
301
+ begin
302
+ routes[info[Preference::FIELD_ROUTE_PATH].strip] = Object.const_get(handler).new(info)
303
+ rescue NameError
304
+ raise PreferenceError, PreferenceError::MSG_NO_SUCH_HANDLER% info[Preference::FIELD_ROUTE_HANDLER]
305
+ end
306
+
307
+ paths << path
308
+ end
309
+
310
+ # is there any duplicate path?
311
+ if paths.size != paths.uniq.size
312
+ raise PreferenceError, PreferenceError::MSG_DUPLICATE_PATH% Preference.instance.path_route_yml
313
+ end
314
+
315
+ run URLMapper.new routes
316
+ end # end proc
317
+ end # end #define_route
318
+ end
319
+ end