nekonote-framework 1.0.0.pre.beta

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.
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